En algèbre, comme dans la formation des concepts quotidiens, les abstractions sont formées en regroupant les choses par certaines caractéristiques essentielles et en omettant leurs autres caractéristiques spécifiques. L'abstraction est unifiée sous un seul symbole ou mot dénotant les similitudes. Nous disons que nous abstrait sur les différences, mais cela signifie vraiment que nous sommes intégrant par les similitudes.
Par exemple, considérons un programme qui prend la somme des nombres 1
, 2
et 3
:
val sumOfOneTwoThree = 1 + 2 + 3
Ce programme n'est pas très intéressant, car il n'est pas très abstrait. On peut abstrait sur les nombres que l'on additionne, en intégrant toutes les listes de nombres sous un seul symbole ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
Et nous ne nous soucions pas particulièrement du fait qu'il s'agisse d'une liste. List est un constructeur de type spécifique (prend un type et retourne un type), mais nous pouvons abstrait sur le constructeur de type en spécifiant la caractéristique essentielle que nous voulons (qu'il puisse être plié) :
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)
Et nous pouvons avoir des implicites Foldable
instances pour List
et toute autre chose que nous pouvons plier.
implicit val listFoldable = new Foldable[List] {
def foldl[A, B](as: List[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)
}
implicit val setFoldable = new Foldable[Set] {
def foldl[A, B](as: Set[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)
}
val sumOfOneTwoThree = sumOf(List(1,2,3))
De plus, nous pouvons abstrait sur à la fois l'opération et le type des opérandes :
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)
Maintenant, nous avons quelque chose d'assez général. La méthode mapReduce
pliera tout F[A]
étant donné que nous pouvons prouver que F
est pliable et que A
est un monoïde ou peut être transformé en un monoïde. Par exemple :
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(Set(4,5,6), Product)
Nous avons Abstraction faite de monoïdes et pliables.