Today’s Scalera tip (and it’s been a while since last published tip) is about one of the Scala 2.12 version features that we fancy the most regarding the possibility of avoiding too much boilerplate.
Uncle SAM
Let’s suppose we’ve defined a type class that gathers certain amount of common implemented functionality like:
trait Comparisson[T]{ def compare(t1: T, t2: T): Int def greaterThan(t1: T, t2: T): Boolean = compare(t1, t2) > 0 def greaterEqualThan(t1: T, t2: T): Boolean = compare(t1, t2) >= 0 def equalTo(t1: T, t2: T): Boolean = compare(t1, t2) == 0 def lowerThan(t1: T, t2: T): Boolean = compare(t1, t2) < 0 def lowerEqualThan(t1: T, t2: T): Boolean = compare(t1, t2) <= 0 }
…in that case the remaining job would consist on defining the compare
method.
For creating the different instances of Comparisson
we could either create implicit val
s or implicit object
s (nothing new about type classes so far):
object Comparisson { implicit val intComparisson: Comparisson[Int] = new Comparisson[Int]{ def compare(t1: Int, t2: Int): Int = t1 - t2 } implicit val booleanComparisson: Comparisson[Boolean] = new Comparisson[Boolean] { def compare(t1: Boolean, t2: Boolean): Int = { List(t1, t2) .map(b => if (b) 1 else 0) .reduce(_ - _) } } }
Defining anonymous Comparisson
instances (or extending from that trait in case of object
s) was the only way of defining these instances so far.
With Scala’s 2.12 version a new concept known as SAM (Single Abstract Method) comes up. It basically allows defining an anonymous instance by providing a function equivalent to the only abstract method of the trait/abstract class.
When applying to the proposed example:
object Comparisson { implicit val intComparisson: Comparisson[Int] = (t1: Int, t2: Int) => t1 - t2 implicit val booleanComparisson: Comparisson[Boolean] = (t1: Boolean, t2: Boolean) => List(t1, t2) .map(b => if (b) 1 else 0) .reduce(_ - _) }
Cute, right? Just as a reminder, do have in mind that if we don’t explicitly type the expression, the compiler won’t understand that it has to do its magic and it will assume that what we’ve just implicitly defined is a mere function:
object Comparisson { implicit val intComparisson = (t1: Int, t2: Int) => t1 - t2 // The previous inferred type will be (Int, Int) => Int }
And we’re not telling this because it already happened to us…
Peace out!