08/09/2011
oxmo456

Implicit

Scala m’a une nouvelle fois ébloui ! laissez moi vous raconter cette histoire.
Dans le dernier billet, je n’était pas parvenu a trouver un équivalant de l’opérateur |> (forward pipe operator) de F#. Cet opérateur n’existant pas en Scala, je ne risquais pas de le trouver… et là, lundi matin (de la semaine dérniere), par hazard, je tombe sur cet opérateur dans une question relative à Scala sur Stackoverflow !

L’opérateur n’existe pas, mais Scala nous permet de le créer d’une maniere très simple !

Nous allons commencer avec un rappel de la définition de l’opérateur |>. Cet opérateur passe le résultat de l’opérande situé à gauche de l’opérateur vers la fonction située à droite de celui ci. Un example pour comprendre le fonctionnement :

function plus1(value:int):int{
   return value + 1
}
//sans opérateur |>
plus1(plus1(1))
//avec l'opérateur |> (beaucoup plus lisible)
1 |> plus1 |> plus1 //3

Abordons maintenant les opérateurs en Scala. En Scala, ce qui pourrait être considérer comme un opérateur, n’en est pas vraiment un ! Prenons par exemple le bon vieux + et l’expression suivante :

1 + 1
//res0: Int = 2

A premiere vue nous somme bien en présence d’un opérateur (au sens traditionnel du terme). Et bien non ! le petit + est en réalité une méthode de la classe Int. La preuve par l’exemple :

1.+(1)
//res0: Double = 2.0

(Vous aurrez peut être constaté que nous obtenons un Double en sortie, l’explication est ICI)

En Scala, presque tout ce qui resemble à un opérateur est en réalité une méthode. Le fait de pouvoir omettre le point et les parenthèses (dans certaine conditions), ainsi que la possibilité de nommer les méthodes avec des caracteres spéciaux nous permet de faire passer les méthodes pour des opérateurs.

Revenons à nos moutons ! Pour créer l’opérateur |>, il faudrait pouvoir ajouter à toutes les classes (Int, String, Boolean, Foo, Bar…) la méthode |> (comme pour le +). Voici  le code qui permet de réaliser ce miracle  :

package eu.badmood

object Utils {

  class ForwardPipedObject[T] private[Utils] (value:T){
    def |>[R](f:T=>R) = f(value)
  }

  implicit def toForwardPipedObject[T](value:T) = new ForwardPipedObject[T](value)
}

Détaillons maintenant ces quelques lignes :

package eu.badmood

La premiere ligne est consacrée à la déclaration du package.

object Utils {

Ensuite vient la déclaration de l’objet Utils (un Singleton object, description ici).

class ForwardPipedObject[T] private[Utils] (value:T){

Ici nous déclarons la classe ForwardPipedObject. Le constructeur de cette classe est privé mais reste malgré tout accessible à l’interieur de l’objet Utils. Cette classe fait également usage des génériques (T). Pour finir le constructeur de la classe prend en paramètre objet de type T (type générique).

def |>[R](f:T=>R) = f(value)

Voici la définition de la méthode |>. Cette méthode prend en paramètre une fonction qui prend en entrée un paramètre de type T et retourne un objet de type R (la signature de la fonction fait office de type). Une fois appelée, cette methode execute la fonction passée en paramètre avec pour argument la valeur passée au constructeur de la classe ForwardPipedObject.

implicit def toForwardPipedObject[T](value:T) = new ForwardPipedObject[T](value)

Cette méthode prend en paramètre un objet de type T et créé avec celui ci une instance de la classe ForwardPipedObject. Le fait de marquer cette méthode avec le mot clef implicit, permettra une mise en oeuvre automatique de cette fonction par le compilateur.

Fin de l’explication du code !

Vous aurez peut être compris que ce code n’ajoute la méthode |> à aucune classe (hormis ForwardPipedObject). Par contre nous pouvons maintenant encapsuler tous nos objets dans la classe ForwardPipedObject grace à la méthode toForwardPipedObject . Example :

def plus1(value:Int) = value + 1

toForwardPipedObject(1).|>(plus1)//2

Pas mal, mais nous somme loin de l’opérateur |> de F# ! Continuons ! La méthode toForwardPipedObject étant marquée comme implicite, il n’est plus nécessaire de déclarer explicitement sont utilisation (le compilateur s’en chargera a notre place).

import eu.badmood.Utils.toForwardPipedObject //import de l'implicite

def plus1(value:Int) = value + 1

1 |> plus1 |> plus1 //3

Dans cet exemple, le compilateur va se rendre compte que la méthode |> n’existe pas pour la classe Int. Il va alors chercher si il existe un définition implicite qui permet de résoudre ce problème. Dans notre cas, le compilateur va trouver la méthode implicite toForwardPipedObject qui permet de transformer n’importe quel objet en objet ForwardPipedObject (objet qui possède une définition pour la méthode |>). Le compilateur à pu résoudre son problème ! Sans les implicites nous aurions été contraint d’écrire ceci :

toForwardPipedObject(toForwardPipedObject(1).|>(plus1)).|>(plus1)

Voila, c’est fini. J’espere que  j’ai été assez claire dans mes explications ! et surtout que je n’ai pas raconté trop de bêtises (je suis encore un petit scarabé en Scala !).

Je me suis directement inspiré de cet article pour la création de l’implicite, vous y trouverez également plus d’informations.

Taste of SCALA03/07/2011

Artisan plaquiste-peintre !25/03/2011

DRAWING TRIANGLES01/11/2010

RGB to Vector28/10/2010

RGB24/10/2010

AsUnit : suite et fin21/10/2010

AsUnit : First test (1)04/08/2010

AsUnit: first steps29/07/2010