En algèbre, comme dans la formation quotidienne des concepts, les abstractions sont formées en regroupant les choses selon 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 faisons abstraction des différences, mais cela signifie vraiment que nous intégrons par les similitudes.
Par exemple, envisager 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. Nous pouvons faire abstraction des nombres que nous additionnons, 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 que ce soit une liste non plus. List est un constructeur de type spécifique (prend un type et retourne un type), mais nous pouvons faire abstraction du 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 Foldable
instances implicites pour List
et toute autre chose que nous pouvons replier.
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))
De plus, nous pouvons faire abstraction à la fois de l'opération et du 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 élément F[A]
que nous pouvons prouver qu'il F
est pliable et qui A
est un monoïde ou peut être mappé en un seul. 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(List(4,5,6), Product)
Nous avons résumé les monoïdes et les pliables.