BT

Composition de Kleisli

| Écrit par Othman Doghri Suivre 0 Abonnés le 23 nov. 2015. Durée de lecture estimée: 6 minutes |

La composition des fonctions est une technique en programmation qui permet de construire des fonctions complexes à partir de fonctions simples. Lors de l’exécution, on aura un pipeline dans lequel le résultat de la première fonction est transmis à la deuxième, et ainsi de suite. Cependant, comment construire ce pipeline tout en considérant le risque d’échec lors de l’exécution des fonctions ?

Supposons qu'on voudrait implémenter la composition des deux fonctions suivantes :

f(x) = 100/x avec x!=0

g(x) = Log(x) avec x > 0

La composition donne la fonction h(x) :

h(x) = Log(100/x) avec x > 0

Dans cet article, nous allons essayer d’implémenter cette composition afin d’aboutir à une écriture qui soit équivalente à l’écriture mathématique de composition des fonctions : h = g ○ f

Dans ce qui suit, nous allons utiliser Scala comme langage de programmation.

Définition des fonctions

Commençons d’abord par déclarer les fonctions division et logarithme :

def division( x : Double ) : Option[Double] = if (x != 0) Some( 100 / x ) else None
def logarithme(x : Double) : Option[Double] = if (x > 0) Some(Math.log(x)) else None

Ces fonctions retournent une Option, qui est une enveloppe de type pouvant contenir deux cas, soit le cas de succès, soit le cas d'échec. La question qui se pose est : comment composer ces deux fonctions, tout en évitant de gérer à chaque fois le cas d'erreur ?

Méthodes classiques de composition

Il est possible de faire cette composition grâce à la fonction flatMap des Options, la composition des deux fonctions division et logarithme peut s’écrire sous cette forme :

def composition(x : Double) : Option[Double] = division(x) flatMap logarithme

ou avec for-comprehension de Scala :

def composition(x : Double)  : Option[Double] = for{
                                                      y <- division(x)
                                                      z <- logarithme(y)
                                                   } yield z

Bien que ces deux écritures réalisent bien la composition de deux fonctions, elles ne sont pas proches de l’écriture mathématique de composition vers laquelle on voudrait aboutir qui est : h = g ○ f. Le but est d’écrire cette composition sans faire référence aux arguments afin d’arriver à une expression décrite uniquement par les fonctions.

Méthode de composition de Kleisli

Kleisli permet de composer deux fonctions d’une manière abstraite grâce aux opréateurs qu’il propose. En effet, Kleisli est caractérisé par le triplet [M,A,B], tel que :

  • M est une enveloppe de type, M[B] est le type de retour de la fonction.
  • A est le type de départ de la fonction.
  • B est le type du résultat en cas de succès.

Un Kleisli caractérisé par le triplet [M,A,B] est le résultat de l’emballage d’une fonction du type : A => M[B].

En réalité, M[B] est une monade disposant des deux fonctions :

  • unit ou return de type : B => M[B].
  • bind de type : M[B] => (B => M[C]) => M[C] qui est représenté par la fonction flatMap.

Dans la suite, nous allons utiliser Kleisli de la librairie scalaz qui est une librairie scala dédiée à la programmation fonctionnelle.

Dans notre cas, la fonction division est du type : Double => Option[Double] et la fonction logarithme est du type : Double => Option[Double]. Donc, on pourrait très bien les emballer dans un type de Kleisli comme suit :

val divisionK   =  Kleisli {   division   }
val logarithmeK =  Kleisli {  logarithme  }

divisionK et logarithmeK sont deux Kleisli caractérisés par le triplet : [Option,Double,Double].

Le but de la composition est de partir d’un Kleisli caractérisé par le triplet [M,A,B] et d'un Kleisli de [M,B,C] pour aboutir à un kleisli de [M,A,C].

Pour la composition, ces deux conditions doivent être vérifiées :

  • La même enveloppe de type M pour les deux Kleisli.
  • Le type d’arrivée de la fonction du premier Kleisli doit être le type de départ de la fonction du second Kleisli.

Donc, on pourrait très bien composer les fonctions divisionK et logarithmeK d'une manière assez simple en utilisant l'opérateur compose ou <=< de Kleisli :

val compositionK = logarithmeK <=< divisionK

ou avec la fonction compose de kleisli :

val compositionK = logarithmeK compose divisionK

Si on voulait s'approcher d'une écriture qui reflète le plus l'ordre de l'exécution des fonctions, on pourrait envisager d'utiliser l'opérateur >=> de Kleisli :

val compositionK = divisionK >=> logarithmeK

ou avec la fonction andThen de kleisli :

val compositionK = divisionK andThen logarithmeK

Une troisième manière de composition est possible avec l'opérateur >==> et l'opérateur <==< qui est différent des autres types de composition. En fait, il suffit de partir d’un seul Kleisli, par exemple Kleisli de [M,A,B] et de le composer avec une fonction du type B => M[C] pour aboutir à un Kleisli du triplet [M,A,C] :

 val compositionK = divisionK >==> logarithme

ou

val compositionK = logarithmeK  <==< division

Il est à noter que toutes les compositions ci-dessus vont nous conduire aux mêmes résultats. Enfin, il ne reste qu'à appliquer le Kleisli résultant compositionK sur des inputs :

 compositionK(0)       // renvoie None  (échoue à la première fonction division)
    compositionK(-10)     // renvoie None  (échoue à la deuxième fonction logarithme )
    compositionK(100)     // renvoie Some(0.0)
    compositionK(2)       // renvoie Some(3.912023005428146)

Transformation de Kleisli

Disposant de la fonction lireConsole qui permet de lire un Double depuis la console et qui renvoie un Try[Double] :

def lireConsole(u : Unit) : Try [Double] = Try(Console.readLine("Entrer une valeur :").toDouble)

Définissons un Kleisli lireConsoleK :

val lireConsoleK = Kleisli { lireConsole }

La fonction lireConsole est du type : Unit => Try[Double] donc lireConsoleK est un Kleisli caractérisé par le triplet : [Try,Unit,Double], alors qu'on aimerait bien l’enchaîner avec compositionK de type : [Option,Double,Double]. Il faudrait donc transformer Kleisli de [Try,Unit,Double] en un Kleisli de [Option,Unit,Double].

Kleisli donne la possibilité de faire cette transformation avec la fonction mapK. Définissons tout d'abord la fonction paramétrée transform[T] qui permet de transformer Try[T] en Option[T] :

  def transform [T] ( t : Try[T] ) : Option[T]  = t match {
                                                           case Success(e) => Some(e)
                                                           case Failure(e) => None
                                                           }

Par la suite, il suffit de fournir la fonction transform à la fonction mapK afin de créer un Kleisli de [Option,Unit,Double] à partir d’un Kleisli de [Try, Unit, Double]. Finalement, il ne reste qu' à composer la fonction compositionK avec lireConsoleK transformée avec la fonction transform :

val deuxiemeCompositionK =  compositionK <=< (lireConsoleK mapK transform)

Exécutons deuxiemeCompositionK et comparons son résultat avec compositionK :

deuxiemeCompositionK()  //    Entrer une valeur : 2
                        //    Some(3.912023005428146)

compositionK(2)         //    Some(3.912023005428146)

Conclusion

Dans cet article, nous avons montré la composition de Kleisli pour des fonctions qui renvoient un Try ou une Option. Il serait possible de réaliser cette composition pour des fonctions qui renvoient d’autres types monadiques comme Future ou Either. Sous le capot de la composition de Kleisli, il y a des concepts algébriques qui proviennent essentiellement de la théorie des catégories que je vous invite à découvrir.

Références

À propos de l'Auteur

Othman Doghri est principalement développeur autour des technologies Java. Il a participé à l’étude et développement de plusieurs projets de systèmes d’information. Il est passionné par les nouvelles technologies et intéressé par les paradigmes de programmation, particulièrement la programmation fonctionnelle.

Evaluer cet article

Pertinence
Style

Bonjour étranger!

Vous devez créer un compte InfoQ ou cliquez sur pour déposer des commentaires. Mais il y a bien d'autres avantages à s'enregistrer.

Tirez le meilleur d'InfoQ

Donnez-nous votre avis

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet
Commentaires de la Communauté

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet

Discuter

Se connecter à InfoQ pour interagir sur ce qui vous importe le plus.


Récupérer votre mot de passe

Follow

Suivre vos sujets et éditeurs favoris

Bref aperçu des points saillants de l'industrie et sur le site.

Like

More signal, less noise

Créez votre propre flux en choisissant les sujets que vous souhaitez lire et les éditeurs dont vous désirez suivre les nouvelles.

Notifications

Restez à jour

Paramétrez vos notifications et ne ratez pas le contenu qui vous importe

BT