Scalera tip: Handling sticky implicit contexts

A couple of days ago (translation for the masses: like a month ago) I noticed Viktor Klang was tweeting about removing the annoying implicit evidences from methods. And some things I read seemed so elegant to me that I was forced to share some related ideas with all of you that don’t follow him at Twitter (@viktorklang).

Setting some context

Imagine the typical polymorphic method where we need an execution context for evaluating some Future:

import scala.concurrent.{ExecutionContext, Future}

def myMethod[T]
  (element: T)
  (implicit ev: ExecutionContext): Future[Boolean] = ???

You could say it’s as typical as disgusting, having to repeat the same exact words in the following 10 method definitions: (implicit ev: ExecutionContext).

Playing with type alias

The happy idea that is being proposed is to define a type alias like the following one:

type EC[_] = ExecutionContext

This way, by adding some syntax sugar, we would re-define the method signature:

def myMethod[T:EC](element: T): Future[Boolean] = ???
myMethod("hi")

Beautiful, isn’t it?

Some other possibilities

Non-polymorphic methods

In case our method isn’t parameterized, we would have to add some boilerplate (by adding a wildcard for the type that parameterizes the method). In essence, it should be working the same principle:

def myMethod[_:EC](element: Int): Future[Boolean] = ???
myMethod(2)

Multiple implicit contexts

The not-so-crazy case in which we needed several implicit parameters of different natures, we would have to define as many type alias as different type parameters we required:

type EC[_] = ExecutionContext
type MongoDB[_] = MongoDBDatabase

def myMethod[_:EC:MongoDB](element: Int): Future[Boolean] = ???

But what if …?

Multiple implicit parameters with same type

In case we have several implicit parameters that share the same type,

def myMethod
  (element: Int)
  (implicit ev1: ExecutionContext, ev2: ExecutionContext): Future[Boolean] = ???

it turns out that …

Well, by definition that’s impossible given that it would incur in some ambiguity issue when resolving implicits. It’s true that Scala allows having these kind of signatures, but we could only invoke them by making explicit the arguments contained in the second parameter group.:

myMethod(2)(ec1,ec2)

which is kind of…

Type-constructor implicit contexts

When we have implicit parameters that are also type constructors like List[T], Future[T], Option[T]

…well, it actually depends.

Case 1

If the type that parameterizes the method and the one that parameterizes the evidence are not related, there’s no big deal: we define another type alias and move on:

type EC[_] = ExecutionContext
type MongoDB[_] = MongoDBDatabase
type IntOpt[_] = Option[Int]
type StrList[_] = List[String]

def myMethod[_:EC:MongoDB:IntOpt:StrList](
  element: Int): Future[Boolean] = ???

Which would be equivalent to:

def myMethod(
  element: Int)(
  implicit ev1: ExecutionContext,
  ev2: MongoDBDatabase,
  ev3: Option[Int],
  ev4: List[String]): Future[Boolean] = ???

Case 2

If the type that parameterizes the method and the one that parameterizes the evidence have to match …

Well, it’s not possible. The syntax sugar we’re using here implies that both types have to match. Maybe it was too pretty for our bodies 🙂

See you in the next post. Peace out!

Scalera tip: contextos implícitos pegajosos

El otro día (para la gente normal: hace cosa de 1 mes) vi que el gran Viktor Klang twiteaba acerca de como quitar las molestas evidencias implícitas en definiciones de métodos. Y me pareció tan elegantes algunas de las cosas que leí, que me vi en la obligación de compartir algunas ideas al hilo de dichos consejos con aquellos de vosotros que no le sigáis aun en Twitter (@viktorklang).

La situación

Imaginad el típico método polimórfico en el cual necesitamos un execution context para ejecutar un futuro:

import scala.concurrent.{ExecutionContext, Future}

def myMethod[T]
  (element: T)
  (implicit ev: ExecutionContext): Future[Boolean] = ???

Es tan típico como feo, el tener que repetir la coletilla de (implicit ev: ExecutionContext) en 10 métodos seguidos…

Jugando con type alias

La idea feliz que se propone es definir un type alias del siguiente tipo:

type EC[_] = ExecutionContext

De esta forma, re-definiríamos la cabecera de nuestro método como sigue:

def myMethod[T:EC](element: T): Future[Boolean] = ???
myMethod("hi")

¿Bello o no?

Otras posibilidades

Métodos no polifórmicos

En el caso en que nuestro método no esté parametrizado, tendríamos que añadir algo de boilerplate (añadiendo un wildcard para el tipo que parametriza el método), pero en esencia debería seguir funcionando el mismo principio:

def myMethod[_:EC](element: Int): Future[Boolean] = ???
myMethod(2)

Múltiples contextos implícitos

En el no-tan-descabellado caso en el que necesitáramos varios parámetros implícitos de distintos tipos, necesitaríamos definir tantos type alias como tipos distintos de parámetros requiriésemos:

type EC[_] = ExecutionContext
type MongoDB[_] = MongoDBDatabase

def myMethod[_:EC:MongoDB](element: Int): Future[Boolean] = ???

Pero, ¿y si…?

Múltiples parámetros implícitos del mismo tipo

En el caso de que tengamos múltiples parámetros implícitos del mismo tipo,

def myMethod
  (element: Int)
  (implicit ev1: ExecutionContext, ev2: ExecutionContext): Future[Boolean] = ???

ocurriría que …

Bueno, por definición eso es imposible ya que incurriría en un problema de ambigüedad a la hora de resolver implícitos. Es cierto que Scala nos permite este tipo de signaturas, pero sólo podríamos invocar al método haciendo explícitos los argumentos del segundo grupo de parámetros:

myMethod(2)(ec1,ec2)

Lo cual es un tanto…

Contextos implícitos que son constructores de tipos

Cuando tenemos parámetros implícitos que son constructores de tipos como List[T], Future[T], Option[T]

En realidad depende.

Caso1

Si el tipo que parametriza el método y el que parametriza la evidencia no están relacionados, no hay mucho problema: definimos otro type alias y a correr:

type EC[_] = ExecutionContext
type MongoDB[_] = MongoDBDatabase
type IntOpt[_] = Option[Int]
type StrList[_] = List[String]

def myMethod[_:EC:MongoDB:IntOpt:StrList](
  element: Int): Future[Boolean] = ???

Lo cual sería el equivalente a:

def myMethod(
  element: Int)(
  implicit ev1: ExecutionContext,
  ev2: MongoDBDatabase,
  ev3: Option[Int],
  ev4: List[String]): Future[Boolean] = ???

Caso 2

Si el tipo que parametriza el método y el que parametriza la evidencia tienen que concordar …

Bueno no es posible. El syntax sugar implica que el tipo que parametriza el método vaya en concordancia con el tipo que parametriza nuestra evidencia. Quizás era todo demasiado bonito 🙂

Hasta el próximo post. ¡Agur de limón!

ClassTag, Class and war stories…

Sometimes, when working with Scala, the need of working with type metadata just comes out. Even though macros might seem something too far, theorical and tricky (we’ll speak about them later), sometimes getting some info about runtime classes is more than enough.

ClassTag

You could see a ClassTag as some ‘wrapper’ that Scala adds over java.lang.Class runtime classes.

In order to work with ClassTag we need to import:

import scala.reflect.{ClassTag,classTag}

Whereas the first member imported is the class, the second one is just a method that allows to get implicitly the ClassTag of a certain type. I.e.:

scala> classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

Actually, it’s just syntax sugar for:

def classTag[T:ClassTag]: ClassTag[T] = 
  implicitly[ClassTag[T]]

scala> classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

There are methods (currently deprecated by type checking via TypeTag) like <:< or =:= that allow checking at compile time the type bounds.
For example, this:

def myMethod[T,U](t: T,u: U)(implicit ev: <:<[T,U]):Unit = 
  println(t,u)

allows constraining the relation between T and U types:

scala> myMethod(new Animal,new Car)
<console>:11: error: Cannot prove that Animal <:< Car. myMethod(new Animal,new Car)

scala> myMethod(new Cat,new Animal)
($line15.$read$$iw$$iw$Cat@350aac89,$line14.$read$$iw$$iw$Animal@1c5920df)

Where are my type parameters?

One of the main problems working with ClassTag is the information loss about the parameter types that you class might have. For example, if we ask for the ClassTag of List[Int] we’ll get that:

scala> println(scala.reflect.classTag[List[Int]])
scala.collection.immutable.List

As you can see, information about Int that parameterizes the list is lost. This process that removes parameter types info at runtime is well known as type erasure.

62517760

Nevertheless, if we need info about the static type, we can use TypeTags (which should be properly explained at future posts, when talking about macros).

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> val tt = typeTag[List[Int]]
tt: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]

Frequent problems

How to get the Class of a known type T?

scala> classOf[Int]
res0: Class[Int] = int

I have a Class, but I need a ClassTag

As easy as:

scala> val clazz = classOf[Int]
clazz: Class[Int] = int

scala> val ctag = ClassTag(clazz)
ctag: scala.reflect.ClassTag[Nothing] = Int

scala> val ctag = scala.reflect.ClassTag[Int](clazz)
ctag: scala.reflect.ClassTag[Int] = Int

Notice that, unless we explicitly point the type, ClassTag builder will infer the most generic possible type (Nothing).

Is T a subtype of U?

We’ve seen before that, at compile time, we can use <:< and so for constraining type bounds but, what if it takes place at runtime? How can we check it?

We can use isAssignableFrom method, from class Class that indicates whether a class is the same class (or superclass) compared to another one:

object A{
  def apply[T:ClassTag] = new {
    def isA[U:ClassTag]: Boolean = 
      classTag[T].runtimeClass.isAssignableFrom(classTag[U].runtimeClass)
  }
}

We could try this code snippet with:

scala> A[Int].isA[String]
res0: Boolean = false

scala> A[String].isA[String]
res1: Boolean = true

Infer the type of an Any

This tip comes out of a question that a colleague made me. ‘Is there any way to, having the folling function:

def isA[T](t: Any): Boolean = ???

check if t value is T typed?’
First obvious step for checking it would be something similar to:

scala> def isA[T](t: Any): Boolean = t.isInstanceOf[T]
<console>:7: warning: abstract type T is unchecked since it is eliminated by erasure
       def isA[T](t: Any): Boolean = t.isInstanceOf[T]
                                                   ^
isA: [T](t: Any)Boolean

Compiler is warning us (Forewarned is forearmed) that we won’t have information about T at runtime, that it’s getting lost. What if we try with different types? It eats them all:

scala> isA[Int]("")
res2: Boolean = true

scala> isA[Int](3)
res3: Boolean = true

In this case, we said ‘okay, what if we just try to check if classes are assignables, getting the info from the ClassTag‘?
It happened that,

scala> val x: Any = 3
x: Any = 3

scala> x.getClass
res2: Class[_] = class java.lang.Integer

scala> classOf[Int].isAssignableFrom(classOf[Int])
res3: Boolean = true

scala> classOf[Int].isAssignableFrom(classOf[java.lang.Integer])
res4: Boolean = false

The x class is java.lang.Integer, which is not assignable from Int class (the same happens with many other primitive types).

So we finally…

…realized that ClassTag has a wonderfull unapply method that allows to get a typed value if this belongs to the ClassTag parameterized class. Just what we needed:

def isA[T:ClassTag](t: Any): Boolean = 
  classTag[T].unapply(t).isDefined

When we tried it at the REPL, we could gladly check that:

scala> val x: Any = 3
x: Any = 3

scala> isA[String](x)
res5: Boolean = false

scala> isA[Int](x)
res6: Boolean = true

Conclusions

It’s not very common to deal with all these isses unless you’re building a generic enough system. However, it doesn’t harm you to have a little idea about what the dark side is about…

137134463824621

Peace out!

ClassTag, Class y batallitas de la mili…

En ocasiones, al trabajar en Scala, surge la necesidad de trabajar con meta-información sobre los tipos. Si bien las macros pueden parecer algo lejano y demasiado tedioso (ya hablaremos de ellas), muchas veces nos basta con poder obtener algo de información sobre las clases de runtime.

ClassTag

Se trata de un ‘envoltorio’ que añade Scala sobre las clases de runtime de java.lang.Class.

Para trabajar con ClassTag debemos importar:

import scala.reflect.{ClassTag,classTag}

Mientras que el primer valor que se importa es la clase, el segundo es un método para obtener implícitamente el ClassTag de un cierto tipo. Por ejemplo:

scala> classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

En realidad no es más que syntax sugar para lo siguiente:

def classTag[T:ClassTag]: ClassTag[T] = 
  implicitly[ClassTag[T]]

scala> classTag[Int]
res0: scala.reflect.ClassTag[Int] = Int

Disponen de métodos (ahora deprecados por la verificación de tipos mediante TypeTag) como <:< ó =:= que permiten comprobar en tiempo de compilación los bounds de un tipo.
Por ejemplo:

def myMethod[T,U](t: T,u: U)(implicit ev: <:<[T,U]):Unit = 
  println(t,u)

permite restringir la relación entre los tipos T y U:

scala> myMethod(new Animal,new Car)
<console>:11: error: Cannot prove that Animal <:< Car. myMethod(new Animal,new Car) ^ scala> myMethod(new Cat,new Animal)
($line15.$read$$iw$$iw$Cat@350aac89,$line14.$read$$iw$$iw$Animal@1c5920df)

¿Y mis tipos?

Uno de los problemas al trabajar con ClassTag es la pérdida de información sobre los tipos que pueden parametrizar tu clase. Por ejemplo, si preguntamos por el ClassTag de List[Int] obtendremos que:

scala> println(scala.reflect.classTag[List[Int]])
scala.collection.immutable.List

Como podéis ver, se pierde la información sobre el tipo Int que parametriza la lista. Este proceso de borrado de información de tipos en runtime es conocido como type erasure.

62517760

No obstante, en caso de necesitar información estática sobre el tipo, podemos hacer uso de los TypeTag (los cuales introduciremos en futuros posts, cuando hablemos de macros).

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> val tt = typeTag[List[Int]]
tt: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]

Problemas frecuentes

¿Cómo obtener la Class de un tipo T?

scala> classOf[Int]
res0: Class[Int] = int

Tengo un Class, pero necesito un ClassTag

Tan sencillo como

scala> val clazz = classOf[Int]
clazz: Class[Int] = int

scala> val ctag = ClassTag(clazz)
ctag: scala.reflect.ClassTag[Nothing] = Int

scala> val ctag = scala.reflect.ClassTag[Int](clazz)
ctag: scala.reflect.ClassTag[Int] = Int

Fijaos que salvo que indiquemos el tipo de manera explícita, el constructor de ClassTag inferirá el tipo más genérico posible (en este caso Nothing).

¿T es un subtipo de U?

Hemos visto antes, que en tiempo de compilación, podemos usar <:< y similares para restringir los tipos pero, ¿qué ocurre en tiempo de runtime?

Podemos hacer uso del método isAssignableFrom de la case Class que nos indica si una clase es la misma (o superclase) comparado con otra:

object A{
  def apply[T:ClassTag] = new {
    def isA[U:ClassTag]: Boolean = 
      classTag[T].runtimeClass.isAssignableFrom(classTag[U].runtimeClass)
  }
}

Podríamos probar este snippet con:

scala> A[Int].isA[String]
res0: Boolean = false

scala> A[String].isA[String]
res1: Boolean = true

Detectar el tipo a partir de un Any

Este tip surge por una duda que me planteó un compañero acerca de si existía una manera de, teniendo la siguiente función:

def isA[T](t: Any): Boolean = ???

comprobar si el valor t era de tipo T.
El primer paso obvio para comprobarlo sería algo como:

scala> def isA[T](t: Any): Boolean = t.isInstanceOf[T]
<console>:7: warning: abstract type T is unchecked since it is eliminated by erasure
       def isA[T](t: Any): Boolean = t.isInstanceOf[T]
                                                   ^
isA: [T](t: Any)Boolean

El compilador está avisando (y el que avisa no es traidor) que no tenemos información sobre T en tiempo de runtime, que se está perdiendo. ¿Qué ocurre si le pasamos distintos tipos? Se los zampa:

scala> isA[Int]("")
res2: Boolean = true

scala> isA[Int](3)
res3: Boolean = true

En este caso dijimos “bueno, ¿porque no tratamos de comprobar si las clases son asignables entre sí, obteniendo información del ClassTag“?
Pues ocurría que,

scala> val x: Any = 3
x: Any = 3

scala> x.getClass
res2: Class[_] = class java.lang.Integer

scala> classOf[Int].isAssignableFrom(classOf[Int])
res3: Boolean = true

scala> classOf[Int].isAssignableFrom(classOf[java.lang.Integer])
res4: Boolean = false

La clase de x es java.lang.Integer, que no es asignable por parte de Int (lo mismo ocurría con variedad de tipos primitivos).

Al final…

Finalmente nos dimos cuenta de que ClassTag tiene un maravilloso método unapply que permite extraer un valor tipado si este pertenece a la clase del ClassTag. Justo lo que necesitábamos:

def isA[T:ClassTag](t: Any): Boolean = 
  classTag[T].unapply(t).isDefined

al probarlo en la REPL comprobamos con alegría que:

scala> val x: Any = 3
x: Any = 3

scala> isA[String](x)
res5: Boolean = false

scala> isA[Int](x)
res6: Boolean = true

Conclusiones

No es habitual tener que lidiar con estos temas salvo que se estén construyendo sistemas lo suficientemente genéricos. No obstante, nunca está de más echar un vistazo al lado oscuro…

137134463824621

¡Agur de limón!

Implied, Lisa, or Implode?

One of the worst fears a Scalaman may have is implicits: you don’t like them, you avoid them, they’re scary, they’re bloody hell.

satan

And it’s a pitty that, being able to use that powerful tool, you might get in panic and forget about it, avoiding its use. Implicits may result a fancy solution for certain circumstances as we will enum you later.

To have a much better understanding, lets set up a theoretical framwork.

Implicit values

Imagine you are defining ‘identity’ function over integer numbers’ operations (given an integer number and an operation, it should return the same number). Depending on operation type, the value to be used in the operation (add,product) is different. For example, for adding we use zero (0 + n = n) and for multiplying we use 1 (1 * n = n). If you want to avoid passing the neutral element in a explicit way, you can use implicit values as follows.

First of all, we declare an implicit integer that will represent adding neutral element:

implicit val addNeutralElement: Int = 0

And now, we can declare identity function at adding:

def addIdentity(n: Int)(implicit neutral: Int): Int =
  n + neutral

Have a closer look and realize that implicit parameters in the method are declared in a different parameter group, and they are preceded by the reserverd word ‘implicit‘.

Another tip to have in mind is that, what really matters is the argument type instead of its name: compiler will look for an implicit integer within the set up scope.

This can be also applied to methods:

implicit def generateAddIdentity():Int = 0

…and objects…

abstract class NeutralElement(n: Int)
implicit case object AddNeutralElement extends NeutralElement(0)

Implicit ambiguity / Scopes

So let’s say we want to define now the identity function for multiplying. At the same scope, we could define another implicit value for product neutral element:

implicit val addNeutralElement: Int = 0
implicit val productNeutralElement: Int = 1
def addIdentity(n: Int)(implicit neutral: Int): Int =
  n + neutral
def productIdentity(n: Int)(implicit neutral: Int): Int =
  n * neutral

If we try to execute any of both methods…Woops! The compiler will complain about something nearly understandable:

scala> addIdentity(2)
<console>:13: error: ambiguous implicit values:
 both value addNeutralElement of type => Int
 and value productNeutralElement of type => Int
 match expected type Int
              addIdentity(2)
                         ^

What it really means is it doesn’t know which of both implicit values is the needed one: there’s implicit ambiguity. To avoid this, you can define different scopes which are provided by context objects. Something like:

object AddStuff {
  implicit val addNeutralElement: Int = 0
  def addIdentity(n: Int)(implicit neutral: Int): Int =
    n + neutral
}

//in your snippet...

{
  import AddStuff._
  addIdentity(2)
}

I know this is yelling: “I need type-classes!” but we’re not removing these gold minutes from implicits topc. We’re not that much cruel…

Implicit classes

We can also define implicit classes in Scala. Their main target is to extend functionality of certain classes. For example, if we’re using some third parties’ framework, and one of its classes looks like this:

class ThirdParties {
  def method1(): Int = ???
  def method2(n:Int): Boolean = ???
}

we cannot modify its source code, but if we want to add some extra methods to ThirdParties’ class, we can define an implicit class:

implicit class ExtraThirdParties(tp: ThirdParties){
  def method3(n: Int): Boolean =
    !tp.method2(n)
  def method4(): Int = ???
  def method5(): Int =
    tp.method1() * 2
}

This way, when we type down a ‘ThirdParties’ value and a method that doesn’t belong to its class, the compiler will look for implicit classes and/or methods that may fit in such signature. So we’ll be able to use, both originaly defined methods and the new ones (those we have just implemented in the implicit class):

val myTp = new ThirdParties
myTp.method1()
myTp.method2(5)
myTp.method3(2)
myTp.method4()
myTp.method5()

Use cases

So the million dollar question is, in which cases we can use this powerful swiss knife?

* Setting up execution contexts
* Providing additional information about parameter types (type classes)
* Generating DSLs
* Extending a class functionality (implicit classes)

Providing an example of all these cases would be really tough, so we will take a look at some of them in future posts.

giphy

Peace out! 🙂

¿Implícito, Lisa, o ilícito?

Uno de los mayores temores de un iniciado a Scala es el uso de implícitos: no gustan, se evitan, dan miedo, se envían a Satán.

satan

Y es una pena que disponiendo de tan poderosa herramienta, por una cuestión de pánico escénico, se le relegue a un segundo plano, o no se use. Los implícitos pueden resultar una solución elegante para determinadas circunstancias que citaremos posteriormente.

Para comprender mejor a la criatura, establezcamos un marco teórico.

Valores implícitos

Imaginemos que estamos definiendo la función ‘identidad’ sobre las operaciones de los números enteros (Dado un entero y una operación, devolver el mismo número entero). En base al tipo de operación, el valor por el cual hay que operar (suma, multiplicación,…) es distinto. Por ejemplo, para la adición empleamos el 0 (0 + n = n) y para la multiplicación usamos 1 (1 * n = n). Para evitar pasar este elemento neutro de manera explícita podríamos hacer uso de los implícitos como sigue.

Primero nos declaramos un número entero implícito que representará nuestro elemento neutro para la suma:

implicit val addNeutralElement: Int = 0

Y ahora declaramos la operación identidad en la suma:

def addIdentity(n: Int)(implicit neutral: Int): Int = 
  n + neutral

Fijaros bien que los parámetros implícitos en el método se declaran en otro grupo de argumentos y precedidos de la palabra reservada ‘implicit‘.
Otra observación a tener en cuenta es que lo importante no es el nombre del argumento en el método, sino el tipo: el compilador buscará en el ámbito del método, un valor implícito de tipo entero

Este mismo principio también aplica a métodos:

implicit def generateAddIdentity():Int = 0

…y objetos…

abstract class NeutralElement(n: Int)
implicit case object AddNeutralElement extends NeutralElement(0)

Implicit ambiguity / Scopes

Supongamos ahora que queremos definir la función identidad para la multiplicación. En el mismo ámbito podríamos definir otro valor implícito para el elemento neutro en la multiplicación:

implicit val addNeutralElement: Int = 0
implicit val productNeutralElement: Int = 1
def addIdentity(n: Int)(implicit neutral: Int): Int = 
  n + neutral
def productIdentity(n: Int)(implicit neutral: Int): Int = 
  n * neutral

Si tratamos de ejecutar cualquiera de los dos métodos…Woops! El compilador chilla algo ininteligible:

scala> addIdentity(2)
<console>:13: error: ambiguous implicit values:
 both value addNeutralElement of type => Int
 and value productNeutralElement of type => Int
 match expected type Int
              addIdentity(2)
                         ^

Lo que viene a querer decir es que no sabe cual de los dos valores implícitos debe tomar, es decir, existe ambigüedad de implícitos. Para evitar esto, se definen ámbitos que suelen venir dados por objetos. Algo del siguiente estilo:

object AddStuff {
  implicit val addNeutralElement: Int = 0
  def addIdentity(n: Int)(implicit neutral: Int): Int = 
    n + neutral
}

//in your snippet...

{
  import AddStuff._
  addIdentity(2)
}

Ya se que esto pide a gritos una type-class, pero no robemos el minuto de fama a los implícitos…

Implicit classes

También podemos declarar clases implícitas en Scala. La función principal de esta herramienta es la de extender funcionalidad de determinadas clases. Por ejemplo, supongamos que estamos usando un framework de un tercero, y una de sus clases tiene la siguiente pinta:

class ThirdParties {
  def method1(): Int = ???
  def method2(n:Int): Boolean = ???
}

Como no podemos modificar el código fuente de este framework, pero queremos añadir nuevos métodos a la clase ‘ThirdParties’ podemos definir una clase implícita como sigue:

implicit class ExtraThirdParties(tp: ThirdParties){
  def method3(n: Int): Boolean = 
    !tp.method2(n)
  def method4(): Int = ???
  def method5(): Int = 
    tp.method1() * 2
}

De esta manera, cuando escribamos un valo de tipo ‘ThirdParties’ seguido de un método que no pertenece a su clase, el compilador buscará clases y/o métodos implícitos que encajen en dicha signatura. Así podremos usar, tanto los métodos definidos originalmente por la clase ‘ThirdParties’, como los métodos complementarios que acabamos de definir en la clase implícita:

val myTp = new ThirdParties
myTp.method1()
myTp.method2(5)
myTp.method3(2)
myTp.method4()
myTp.method5()

Casos de uso

La pregunta del millón entonces es, ¿en qué casos podemos aplicar esta poderosa navaja suiza?

* Establecer contextos de ejecución
* Aportar información adicional sobre un parameter type (type classes)
* Generar DSLs
* Ampliar funcionalidad de una clase (implicit classes)

Dado que poner ejemplos de todos ellos sería muy extenso, veremos algunos de estos casos prácticos en futuros posts.

giphy

¡Agur de limón! 🙂