在代数中,就像在日常概念形成中一样,抽象是通过按某些基本特征对事物进行分组并忽略其特定的其他特征来形成的。抽象统一在表示相似之处的单个符号或单词下。我们说我们对差异进行了抽象,但这实际上意味着我们正在通过相似之处进行整合。
例如,考虑一个程序,它的数字的总和1
,2
和3
:
val sumOfOneTwoThree = 1 + 2 + 3
该程序不是很有趣,因为它不是很抽象。通过将所有数字列表集成在单个符号下,我们可以抽象出要求和的数字ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
而且,我们也不在乎它是否也是列表。List是一个特定的类型构造函数(接受一个类型并返回一个类型),但是我们可以通过指定我们想要的基本特征(可以折叠)来抽象该类型构造函数:
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)
现在我们有了一些通用的东西。只要我们可以证明它是可折叠的并且是一个monoid或可以映射为一个,就可以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)
我们已经对 monoid和可折叠对象进行了抽象。