Type currying en Scala

Le système de type de Scala est réputé pour être plus riche que la plupart des autres langages. Entre les view bounds, type alias, abstract type et j’en passe, un nouvel univers s’offre au développeur : le type programming. Ce dernier se différencie du value programming par le fait que les traitements se font, non pas à l’exécution, mais à la compilation. Vous en avez sûrement déjà fait sans vous en rendre compte, en manipulant des types génériques par exemple. Des bibliothèques comme Shapeless vise d’ailleurs à pleinement tirer parti du système de type de Scala. Devant cette jungle de types, force est de constater qu’il devient facile de se perdre… Pour ce post je vais me contenter d’aborder une petite astuce que vous serez peut-être contraint d’utiliser un jour.

Un peu de vocabulaire

Si vous souhaitez plus d’informations sur les types que vous manipulez, le REPL est à votre disposition avec la commande kind :

scala> :kind -v Map
scala.collection.immutable.Map’s kind is F[A1,+A2]
* -> * -(+)-> *
This is a type constructor: a 1st-order-kinded type.

L’exemple ci-dessus se lit comme un type + un type = un nouveau type.

Ca rentre pas !

Imaginons que vous vouliez remplir la boite aux lettres ci-dessus. Disons que chaque lettre est identifiée par le nom de son envoyeur : on aurait quelque chose comme Map[String, Letter] . Essayez maintenant d’appliquer le type constructor Map au higher kinded type Mailbox:

scala> import scala.language.higherKinds
import scala.language.higherKinds

scala> trait Mailbox[T[_]]
defined trait Mailbox

scala> new Mailbox[Map] {}
<console>:13: error: scala.collection.Map takes two type parameters, expected: one
new Mailbox[Map] {}
^

Type lambda

Clairement, ce que l’on passe a un type parameter de trop…

scala> trait Mailbox[T[_, _]]
defined trait Mailbox

scala> new Mailbox[Map] {}
res6: Mailbox[scala.collection.Map] = $anon$1@34cf294c

La solution est simple, mais comment faire si on ne peut pas modifier le trait Mailbox ?

scala> trait MapMailbox[K] extends Mailbox[({type L[V] = Map[K, V]})#L]
defined trait MapMailbox

Cette astuce s’appelle un type lambda. Il s’agit en fait de l’utilisation d’un structural type avec un type projection.

Le structural type (les accolades à la place du type) sert ici à créer une classe anonyme qui contient un type alias  L[V] pour Map[K, _] .  Le type projection sert quant à lui à récupérer ce dernier. En effet, le symbole dièse en type programming est l’équivalent du point dans le monde objet. La syntaxe utilisée est certes effrayante mais sachez qu’il existe un plugin pour le compilateur afin de rendre cette dernière un peu plus user-friendly.

Voici un exemple plus complet en action :

Le type de la clé est donc fourni lors de l’instanciation de la classe, tandis que celui de la valeur à l’appel de la méthode !

Share on Google+Tweet about this on TwitterShare on LinkedInShare on RedditShare on FacebookEmail this to someone

Laisser une réponse

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *