En el tip de Scalera de hoy (y ha llovido ya un tanto desde el último que publicamos) hablaremos sobre una de las features de Scala 2.12 que nos ha parecido bastante interesante de cara a evitar escribir boilerplate
El tio SAM
Supongamos que hemos definido una type class que tiene bastante funcionalidad común del tipo:
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 }
…en cuyo caso solo nos restaría definir el método compare
.
Para crear las distintas instancias de Comparisson
bien podemos crear implicit val
s o implicit object
s (hasta aquí nada nuevo sobre type classes en Scala):
object Comparission { 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(_ - _) } } }
Definir instancias anónimas de Comparisson
(o extender de dicho trait para el caso de object
s) era la única forma de definir estas instancias hasta el momento.
Con la versión 2.12 de Scala surge el concepto de SAM (Single Abstract Method) que básicamente permite definir una instancia anónima aportando una función de orden superior equivalente al único método abstracto en el trait/abstract class.
Si aplicamos al caso anterior quedaría algo como:
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(_ - _) }
Cuqui, ¿no? Simplemente como recordatorio, tened en cuenta que si no anotamos el tipo de manera específica, el compilador no entiende que tiene que hacer su magia y asumira que lo que hemos definido de manera implícita es una función:
object Comparisson { implicit val intComparisson = (t1: Int, t2: Int) => t1 - t2 // The previous inferred type will be (Int, Int) => Int }
Y no es que lo recordemos porque nos haya pasado a nosotros….
¡Agur de limón!