¿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! 🙂

Anuncios

One thought on “¿Implícito, Lisa, o ilícito?

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s