In algebra, as in the formation of the everyday concept, abstractions are formed by grouping units according to some essential characteristics and omitting their specific other characteristics. Abstraction is combined under one symbol or word denoting their similarity. We say that we abstract from differences, but that does mean that we integrate by similarity.
For example, consider a program that takes the sum of the numbers 1 , 2 and 3 :
val sumOfOneTwoThree = 1 + 2 + 3
This program is not very interesting, since it is not very abstract. Thus, we can abstract by specific numbers, combining all lists of numbers under one ns character:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
And we don’t particularly care that this is a list. List is a specific type constructor (accepts a type and returns a type), but we can abstract from the type constructor by indicating what essential characteristic we want (so that it can be collapsed):
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)
And we can have implicit Foldable instances for List and any other thing that we can discard.
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))
What else, we can abstract both by operation and by type of operands:
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)
Now we have something in common. The mapReduce method will discard any F[A] , given that we can prove that F is folding and that A is a monoid or can be mapped to one. For example:
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)
We abstracted over monoids and folds.