Na álgebra, como na formação de conceitos cotidianos, as abstrações são formadas pelo agrupamento de coisas por algumas características essenciais e omissão de suas outras características específicas. A abstração é unificada sob um único símbolo ou palavra denotando as semelhanças. Dizemos que abstraímos as diferenças, mas isso realmente significa que estamos nos integrando pelas semelhanças.
Por exemplo, considere um programa que leva a soma dos números 1
, 2
e 3
:
val sumOfOneTwoThree = 1 + 2 + 3
Este programa não é muito interessante, pois não é muito abstrato. Podemos abstrair os números que estamos somando, integrando todas as listas de números em um único símbolo ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
E não nos importamos particularmente se é uma lista também. List é um construtor de tipo específico (pega um tipo e retorna um tipo), mas podemos abstrair o construtor de tipo especificando qual característica essencial queremos (que pode ser dobrada):
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)
E podemos ter Foldable
instâncias implícitas para List
qualquer outra coisa que possamos dobrar.
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))
Além do mais, podemos abstrair a operação e o 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)
Agora temos algo bastante geral. O método mapReduce
dobrará qualquer F[A]
dado que possamos provar que F
é dobrável e que A
é um monóide ou pode ser mapeado em um. Por exemplo:
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)
Temos abstraída sobre monoids e foldables.