Trong đại số, cũng như trong quá trình hình thành khái niệm hàng ngày, các khái niệm trừu tượng được hình thành bằng cách nhóm các sự vật theo một số đặc điểm thiết yếu và bỏ qua các đặc điểm cụ thể khác của chúng. Sự trừu tượng được thống nhất dưới một biểu tượng hoặc từ biểu thị những điểm tương đồng. Chúng tôi nói rằng chúng tôi trừu tượng hóa những điểm khác biệt, nhưng điều này thực sự có nghĩa là chúng tôi đang tích hợp những điểm tương đồng.
Ví dụ, hãy xem xét một chương trình mà có tổng các số 1
, 2
và 3
:
val sumOfOneTwoThree = 1 + 2 + 3
Chương trình này không thú vị lắm, vì nó không trừu tượng lắm. Chúng tôi có thể tóm tắt các số mà chúng tôi đang tính tổng, bằng cách tích hợp tất cả danh sách các số dưới một biểu tượng duy nhất ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
Và chúng tôi không đặc biệt quan tâm rằng đó cũng là một Danh sách. Danh sách là một phương thức khởi tạo kiểu cụ thể (nhận một kiểu và trả về một kiểu), nhưng chúng ta có thể trừu tượng hóa phương thức khởi tạo kiểu bằng cách chỉ định đặc tính thiết yếu nào chúng ta muốn (có thể gấp lại):
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)
Và chúng tôi có thể có các Foldable
phiên bản ngầm cho List
và bất kỳ thứ gì khác mà chúng tôi có thể gấp lại.
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))
Hơn nữa, chúng ta có thể tóm tắt cả hoạt động và kiểu của toán hạng:
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)
Bây giờ chúng ta có một cái gì đó khá chung chung. Phương pháp mapReduce
này sẽ gấp bất kỳ thứ nào F[A]
cho trước mà chúng ta có thể chứng minh rằng F
có thể gấp lại được và đó A
là một đơn nguyên hoặc có thể được ánh xạ thành một. Ví dụ:
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)
Chúng tôi đã tóm tắt về monoids và gập lại được.