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!

Anuncios

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!

Bounds: A View over your Context…*

Has it ever happened to you that you’re with your colleagues and you feel the need of expressing better parameterizing your methods and classes?

I-know-that-feel (1)

Context bounds

Context bounds allow defining constraints about parameter types inheritance.
For example, let’s suppose the classical inheritance:

trait Animal
trait Cat extends Animal
trait Dog extends Animal

If we want to define a method with an any-kind-of-animal argument, we could define that method as follows:

def myMethod(animal: Animal): Unit =
  println(animal)

But, using parameter types, we could also do it this way:

def myMethod[T<:Animal](animal: T): Unit =
  println(animal)

With <: notation, we force the type T to be a subtype of Animal.
So now, what’s the benefit of using the second notation instead of using the first one? Let’s free our minds a little bit…

post-28553-Steve-Jobs-mind-blown-gif-HD-T-pVbd

What happens if our method receives a Set[Animal] instead of an Animal?

Well, we have to admit, due to invariance, that a Set[Animal] is not the same as a Set[_<:Animal], and we would have to declare our method using one of the following options:

def myMethod[T<:Animal](animals: Set[T]): Unit =
  animals.foreach(println)

def myMethod(animals: Set[_<:Animal]): Unit =
  animals.foreach(println)

We can also define constraints so that the expressed type is a supertype of Dog, for example:

def myType[T>:Dog](animals:Set[T]): Unit =
  animals.foreach(println)
myType(Set(new Animal{})) //ok!

View bounds

If we don’t need to define inheritance constraints, but we want a way to convert the parameter type to something else, then what we’re looking for are view bounds. Their notation is <%.
For example:

def myMethod[T<%Double](doubleLikes: Set[T]): Unit=
  douleLikes.foreach(d => println(d + 2.0))

It means that our method is parameterized by a type T and, in current scope, there must exist a conversion function from T to Double. It’s just syntactic sugar for:

def myMethod[T](doubleLikes: Set[T])(implicit ev: T => Double): Unit =
  douleLikes.foreach(d => println(ev(d) + 2.0))

This way, we could invoke our method like this:

myMethod(Set(1,2,3))//ok
myMethod(Set(true,false))//fail!
<console>:9: error: No implicit view available from Boolean => Double.
              myMethod(Set(true,false,true))
               ^

If there is no view for a concrete type, like in this case for Boolean, we can define one:

implicit def toD(b: Boolean):Double = 
  if (b) 1.0 else 2.0
myMethod(Set(true,false))//ok

And that’s all folks! Easy peasy lemon squeezy 🙂

Peace out.

Bounds: A View over your Context…

¿No te ha pasado alguna vez, cuando estás con los amigos, que sientes la necesidad de expresarte mejor al parametrizar tus métodos y clases?

I-know-that-feel (1)

Context bounds

Los context bounds permiten definir restricciones sobre la herencia de los parameter types.
Por ejemplo, supongamos que tenemos la clásica herencia:

trait Animal
trait Cat extends Animal
trait Dog extends Animal

Si queremos definir un método que reciba como argumento cualquier animal podríamos definir el método como sigue:

def myMethod(animal: Animal): Unit =
  println(animal)

Pero usando parameter types, también podríamos hacerlo como sigue:

def myMethod[T<:Animal](animal: T): Unit =
  println(animal)

Con la notación <: forzamos a que el tipo T sea un subtipo de Animal.
Ahora bien, ¿qué aporta entonces la segunda notación frente a la primera? abramos un poco la mente…

post-28553-Steve-Jobs-mind-blown-gif-HD-T-pVbd

¿Qué ocurre si en vez de recibir un Animal nuestro método, recibiera un Set[Animal]?

Pues que no es lo mismo un Set[Animal] que un Set[_<:Animal], por lo que tendríamos que definir nuestro método siguiendo alguna de las siguientes opciones:

def myMethod[T<:Animal](animals: Set[T]): Unit =
  animals.foreach(println)

def myMethod(animals: Set[_<:Animal]): Unit =
  animals.foreach(println)

También podemos definir restricciones para que el tipo recibido sea un supertype de Dog, como por ejemplo:

def myType[T>:Dog](animals:Set[T]): Unit =
  animals.foreach(println)
myType(Set(new Animal{})) //ok!

View bounds

Si no necesitamos definir restricciones de herencia, pero queremos que exista un modo de convertir el parameter type a otra cosa, entonces lo que estamos buscando son los view bounds. Su notación es <%.
Por ejemplo:

def myMethod[T<%Double](doubleLikes: Set[T]): Unit=
  douleLikes.foreach(d => println(d + 2.0))

Lo que signfica es que nuestro método está parametrizado por un tipo T y que, en el scope actual, debe haber una función de conversión de T a Double. No es sino syntactic sugar para:

def myMethod[T](doubleLikes: Set[T])(implicit ev: T => Double): Unit =
  douleLikes.foreach(d => println(ev(d) + 2.0))

De manera que podríamos invocar nuestro método como sigue:

myMethod(Set(1,2,3))//ok
myMethod(Set(true,false))//fail!
<console>:9: error: No implicit view available from Boolean => Double.
              myMethod(Set(true,false,true))
               ^

Si no existe una vista para un tipo concreto, como en este caso para Boolean, nos podemos definir una:

implicit def toD(b: Boolean):Double = 
  if (b) 1.0 else 2.0
myMethod(Set(true,false))//ok

Y nada más por hoy. Fácil y sencillo 🙂

¡Agur de limón!