В алгебрі, як і в повсякденному формуванні концепцій, абстракції формуються шляхом групування речей за деякими суттєвими ознаками та опускання їх специфічних інших характеристик. Абстракція уніфікується під одним символом або словом, що позначає подібність. Ми говоримо, що абстрагуємось від відмінностей, але це насправді означає, що ми інтегруємо подібності.
Наприклад, розглянемо програму , яка приймає суми чисел 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)
Ми абстрагувались від моноїдів та складок.