代数では、日常の概念形成と同様に、抽象化はいくつかの本質的な特性によって物事をグループ化し、それらの他の特定の特性を省略して形成されます。抽象化は、類似点を表す単一の記号または単語の下で統一されます。私たちは違いを抽象化すると言いますが、これは本当に類似性によって統合していることを意味します。
例えば、数字の合計を取るプログラムを考え1
、2
と3
:
val sumOfOneTwoThree = 1 + 2 + 3
このプログラムはあまり抽象的ではないため、あまり興味深いものではありません。数値のすべてのリストを1つの記号の下に統合することにより、合計している数値を抽象化できますns
。
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
そして、それがリストであることも特に気にしません。リストは特定の型コンストラクター(型を取り、型を返す)ですが、必要な特性(折りたたみ可能)を指定することで、型コンストラクターを抽象化できます。
trait Foldable[F[_]] {
def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
}
def sumOf[F[_]](ns: F[Int])(implicit ff: Foldable[F]) =
ff.foldl(ns, 0, (x: Int, y: Int) => x + y)
また、フォールドできる他のすべての暗黙のFoldable
インスタンスを持つことができますList
。
implicit val listFoldable = new Foldable[List] {
def foldl[A, B](as: List[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)
}
val sumOfOneTwoThree = sumOf(List(1,2,3))
さらに、演算とオペランドのタイプの両方を抽象化できます。
trait Monoid[M] {
def zero: M
def add(m1: M, m2: M): M
}
trait Foldable[F[_]] {
def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
def foldMap[A, B](as: F[A], f: A => B)(implicit m: Monoid[B]): B =
foldl(as, m.zero, (b: B, a: A) => m.add(b, f(a)))
}
def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
今、私たちは非常に一般的なものを持っています。このメソッドは、折りたたみ可能であること、およびモノイドであること、またはモノイドにマッピングできることを証明できる場合は、mapReduce
すべて折りたたみます。例えば:F[A]
F
A
case class Sum(value: Int)
case class Product(value: Int)
implicit val sumMonoid = new Monoid[Sum] {
def zero = Sum(0)
def add(a: Sum, b: Sum) = Sum(a.value + b.value)
}
implicit val productMonoid = new Monoid[Product] {
def zero = Product(1)
def add(a: Product, b: Product) = Product(a.value * b.value)
}
val sumOf123 = mapReduce(List(1,2,3), Sum)
val productOf456 = mapReduce(List(4,5,6), Product)
モノイドと折りたたみ式を抽象化しました。