En álgebra, como en la formación de conceptos cotidianos, las abstracciones se forman agrupando unidades por algunas características esenciales y omitiendo sus otras características específicas. La abstracción está unificada bajo un único símbolo o palabra que denota sus similitudes. Decimos que tenemos resumen sobre las diferencias, pero esto realmente significa que 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. Así podemos abstracta más los números específicos, mediante la integración de todas las listas de números bajo un único símbolo ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
Y no me importa mucho que se trata de una lista tampoco.Lista es un constructor de tipo específico (toma un tipo y devuelve un tipo), pero podemos abstracto sobre el constructor de tipo especificando qué característica esencial que queremos (que puede ser doblada):
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 nosotros puede tener instancias Foldable
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))
Lo que es más, podemos abstracto sobre tanto la operación como el tipo de los 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 podemos probar que F
es plegable y que A
es un monoide o 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)
Tenemos abstraído sobre monoides y Plegables.
@coubeatczech El código se ejecuta en REPL fine. ¿Qué versión de Scala estás usando y qué error obtuviste? –
@Apocalisp Sería interesante si hiciera uno de los dos ejemplos finales un 'conjunto 'o algún otro tipo plegable. Un ejemplo con un 'String' y concatenación también sería genial. –
Hermosa respuesta, Runar. ¡Gracias! Seguí la sugerencia de Daniel y creé implícitos setFoldable y concatMonoid, sin alterar mapReduce. Estoy en camino de asimilar esto. –