En álgebra, como en la formación de conceptos cotidianos, las abstracciones se forman agrupando cosas por algunas características esenciales y omitiendo sus otras características específicas. La abstracción se unifica bajo un solo símbolo o palabra que denota las similitudes. Decimos que abstraemos las diferencias, pero esto realmente significa que nos estamos integrando por las similitudes.
Por ejemplo, considere un programa que toma la suma de los números 1
, 2
y 3
:
val sumOfOneTwoThree = 1 + 2 + 3
Este programa no es muy interesante, ya que no es muy abstracto. Podemos abstraer los números que estamos sumando, integrando todas las listas de números bajo un solo símbolo ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
Y tampoco nos importa especialmente que sea una Lista. List es un constructor de tipo específico (toma un tipo y devuelve un tipo), pero podemos abstraer el constructor de tipo especificando qué característica esencial queremos (que se puede plegar):
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)
Y podemos tener Foldable
instancias implícitas para List
y cualquier otra cosa que podamos plegar.
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))
Además, podemos abstraer tanto la operación como el tipo de operandos:
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)
Ahora tenemos algo bastante general. El método mapReduce
doblará cualquier F[A]
dado que podamos probar que F
es plegable y que A
es un monoide o que se puede mapear en uno. Por ejemplo:
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)
Hemos resumido sobre monoides y plegables.