Valores y funciones implícitas en Scala

Scala permite definir valores, funciones y objetos implícitos. Esto es: que se pueden usar sin necesidad de ser llamados directamente (explícitamente).  Aquí nos ocuparemos de los primeros dos, y hablaré de los objetos implícitos en otro tema donde son ampliamente usados.

Para ello, usamos la palabra reservada implicit antes de la declaración. Aquí un ejemplo:

implicit val listOfInts = (1 to 10) toList

Esto define el valor (inmutable) listOfInts, que no es nada más que List(1,2,3,4,5,6,7,8,9,10), pero el hecho de declararlo implícito nos permite que sea usado en cualquier lugar (dentro del alcance del programa en cuestión) en donde una lista de enteros se necesite. Para ello, necesitamos decirle a Scala que la lista de enteros puede ser implícita al momento de declarar el parámetro. Por ejemplo, digamos que queremos una función que nos permita calcular la suma de los enteros incluidos en una lista:

// Noten la declaración del parámetro como "implicit"
def sumElements(implicit l: List[Int]) = l.reduce{_ + _}

Esto nos permite llamar a la función de 2 formas: con o sin parámetro explícito. Como la función declara su parámetro como implícito, en caso de que no se provea Scala buscará un valor implícito del tipo requerido (en este caso una lista de enteros) que haya sido declarado en algún lugar dentro del alcance del programa.

def sumElements(implicit l: List[Int]) = l.reduce {_ + _}

def anotherFunction = {
  implicit val listOfInts = (1 to 10) toList

   // Código que intenta dominar al mundo

  /* Llamando a la función sin parámetros.
     Esto usará listOfInts, ya que está declarada
     como implícita.
   */
  sumElements 


  /*
    Aquí la función es llamada con parámetro explícito.
  */
  sumElements(List(2,4,6,8,10))
}

En caso de que una función tenga más de un parámetro, Scala permite que el último sea declarado implícito siempre y cuando esté en una lista de parámetros diferente. Hay que recordar que Scala permite tener varias listas de parámetros en las funciones, algo que fue mencionado cuando se explicó currying y aplicación parcial de funciones.

/* Esta función recibe 2 parámetros, pero el segundo es declarado implícito
   En vez de ser:
   def printMessage(msg: String, suffix: String)
  
   usamos 2 listas de parámetros
*/

def printMessage(msg: String)(implicit suffix: String) = 
    println("%s %s".format(msg, suffix))


// Ahora podemos llamar a la función con el segundo parámetro implícito.

implicit val naruto = "dattebayo!"

// Imprime "Algo que diga naruto dattebayo!"
printMessage("Algo que diga naruto")

/* Aquí pasamos explícitamente el segundo parámetro.
   Usamos a Lum-chan, de Urusei Yatsura
*/
printMessage("Daisuki da")("ccha!")

Hay que tener cuidado en no declarar dos valores implícitos del mismo tipo, ya que Scala no sabrá cuál debe usar y regresará un error indicándonos eso.

// Esto no compila
implicit val ja = "jajaja"

// Código para intentar dominar al mundo...

implicit val je = "jejeje"

Aquí estamos declarando 2 valores implícitos de tipo String, lo cual no es válido.

También es posible acceder a lo que está en contexto implícito usando implicitly[T]. Por ejemplo:

implicit val l = List("Hola", "a", "todos")

// En algún lugar más adelante

/*
  Como en contexto sólo puede haber un implícito de 
  cada valor, implicitly lo buscará.
  Esto imprime:
  Hola
  a
  todos
*/
implicitly[List[String]] foreach println

Un uso interesante de implícitos es con funciones que permiten transformar de un tipo a otro de forma transparente. Por ejemplo, digamos que queremos agregar una función “inc” a la clase Int. Lo primero que viene a la mente es crear una clase que extienda a Int, pero si Int está declarada como final no hay nada que se pueda hacer.

/* inc originalmente no es método de Int,
   por lo que este código no compilará
   por sí solo
*/

def addOne(x: Int) = x.inc

Para este tipo de casos, Scala nos permite definir una clase que tome como attributo un valor del tipo requerido y agregue la función que queremos definir.

/* Primero declaramos la clase con el tipo
   requerido y agregamos la función que
   deseamos que tenga */

class IncrementableInteger(x: Int) {
  def inc = x + 1
}

Ahora, en vez de tener que crear explícitamente una instancia de esa clase para usar el método inc, lo que hacemos es hacer esa transformación de forma implícita:

/* Con esta declaración, podemos usar inc en
   cualquier Int, sin necesidad de hacer nada
   más
*/

implicit def toIncrementableInteger(x: Int) = new IncrementableInteger(x)

// Esto ya funciona
println(1.inc)

Y con esto, ahora sí podemos usar la función addOne, ya que cuando una clase no tiene un método, Scala buscará si otra clase lo tiene y si hay una conversión implícita de un tipo a esa clase. De ser así, Scala aplica esa conversión y usa el método de la clase que lo contiene. Esta técnica se conoce como “Pimping Types“, algo así como “adornar tipos” o “mejorar tipos”, y es un concepto que también existe en C# bajo el nombre de “extensions”.

Los implícitos parecen ser más “syntactic sugar” que otra cosa, y así como tienen defensores también tienen detractores. Sin embargo, su uso hace que la llamada a muchas funciones sea más clara, con el precio de que la declaración de la misma puede ser un poco más compleja.

Como punto adicional, los implícitos en Scala son necesarios al momento de usar conceptos más avanzados, como typeclasses, de las que hablaré en la siguiente entrada de esta categoría.