В алгебре, как и при формировании повседневных понятий, абстракции образуются путем группирования вещей по некоторым существенным характеристикам и исключения их конкретных других характеристик. Абстракция объединяется одним символом или словом, обозначающим сходство. Мы говорим, что абстрагируемся от различий, но на самом деле это означает, что мы интегрируемся посредством сходства.
Например, рассмотрим программу , которая принимает суммы чисел 1
, 2
и 3
:
val sumOfOneTwoThree = 1 + 2 + 3
Эта программа не очень интересна, так как не очень абстрактна. Мы можем абстрагироваться от суммируемых чисел, объединив все списки чисел под одним символом 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)
Мы абстрагировались от моноидов и складных объектов.