Parametric polymorphism for functions in Scala
Posted on September 18, 2016
Let’s consider a simple typeclass:
trait Addable[T] {
def plus(x: T, y: T): T
}
implicit object IntAddable extends Addable[Int] {
def plus(x: Int, y: Int) = x + y
}
implicit object ListAddable extends Addable[List[Int]] {
def plus(x: List[Int], y: List[Int]) = x ++ y
}
def plus[T](x: T, y: T)(implicit ev: Addable[T]) = ev.plus(x, y)
List(1, 2, 3).foldLeft(0)(plus) //> Int = 6
List(List(1), List(2), List(3)).foldLeft(List[Int]())(plus) //> List[Int] = List(1, 2, 3)
What happens here is called eta-expansion, and what foldLeft
is getting is this function (e.g. for the former case):
What’s wrong here? We wrap plus
invocation in anonymous function and effectively transfer control twice with required stack reshuffle, and it happens for every element in a collection (details here). Can we do better?
Let’s change just one line:
It still works, only now we instantiate Function2
by calling method plus
once, and this function is passed to foldLeft
. What is lost is the ability to call plus
as a method (i.e. plus(1,2)
).