Functors, Applicatives et Monads en images

Si comme moi avant de commencer Scala vous étiez étranger à la programmation fonctionnelle, vous avez sûrement dû croiser certains termes barbares du milieu tels Functor, Applicative ou Monad. Dans votre recherche du savoir infini vous avez écumé de nombreux articles et vidéos Youtube dans le but d’élucider le mystère. A force de revenir sur les même tutoriaux Haskell obscures ou sur les vidéos de Brian Beckman, vous avez décidé d’en rester là. Heureusement, je suis là pour vous sortir de votre torpeur. Ou plutôt Aditya Bhargava avec cet article très imagé. Les exemples sont certes en Haskell, mais restent compréhensibles.

Voici une valeur toute bête…

Valeur

…Et on sait comment appliquer une fonction à cette valeur :

Application de fonction

Rien de compliqué. Allons plus loin en disant que n’importe quelle valeur peut être dans un contexte. Pour l’instant vous pouvez imaginer un contexte comme une boite vous permettant de stocker une valeur.

value_and_context

Lorsque vous appliquez  une fonction à cette valeur, vous obtiendrez différents résultats en fonction du contexte. Les Functors, Applicatives, Monads, Arrows et autres partent tous de cette idée. Imaginez maintenant un type Maybe, qui définit deux contextes dérivés :

context

Dans un instant, nous allons voir comment l’application d’une fonction diffère en fonction  d’un Just a ou d’un Nothing. Commencons par les Functors.

Functors

Quand une valeur est boxée dans un contexte, vous ne pouvez pas lui appliquer de fonction.

no_fmap_ouch

C’est là que fmap entre en jeu. fmap vient de la rue et s’y connait en contexte. fmap sait comment appliquer des fonctions à des valeurs boxées dans un contexte. Par exemple, supposez que vous voulez appliquer (+3) à Just 2. Utilisez fmap :

fmap_apply

Et bam ! fmap nous montre comment c’est fait ! Mais comment fmap sait appliquer la fonction ?

 Alors en fait, c’est quoi un functor ?

Un Functor est une typeclass. Voici sa définition :

functor_def

Un Functor c’est n’importe quel type qui définit comment fmap s’applique à lui-même. Voyez plutôt :

fmap_def

Donc on peut faire des choses comme :

Et fmap applique magiquement la fonction, car Maybe est un Functor. Il spécifie comment fmap s’applique à Just et Nothing :

Voici ce qui passe en coulisse lorsqu’on écrit  fmap (+3) (Just 2)

fmap_just

Donc là vous faites « Ok fmap, maintenant applique (+3) à un Nothing s’il te plait »

fmap_nothing

 

bill

Tel Morpheus dans Matrix, fmap sait juste quoi faire. Vous commencez avec un Nothing, donc vous finissez avec un Nothing ! fmap est zen. Maintenant on comprend pourquoi le type Maybe existe… Par exemple, voici comment on travaille avec un enregistrement de base de données dans un langage sans Maybe :

Mais en Haskell :

Si findPost retourne un post, on aura le titre avec getPostTitle. S’il retourne Nothing, on retournera Nothing ! Plutôt cool hein ? <$> est la version infix de fmap, donc vous allez souvent voir ça :

Voici un autre exemple : que se passe-t-il lorsqu’on applique une fonction à une liste :

fmap_list

Les listes aussi sont des Functors ! Voici la définition :

Ok, ok, un dernier exemple : que se passe-t-il lorqu’on applique une fonction à une autre fonction ?

Voici une fonction :

function_with_value

 

Et voici une fonction appliquée à une autre fonction :

fmap_function

Le résultat n’est autre qu’une autre fonction !

Donc les fonctions sont aussi des Functors !

A vrai dire, quand vous utilisez fmap sur une fonction, vous faites de la composition de fonctions !

Applicatives

Au niveau suivant nous avons les Applicatives. Avec un Applicative, nos valeurs sont boxées dans un contexte, comme pour les Functors.

value_and_context

Mais nos fonctions sont également boxées dans un contexte !

function_and_context

Ouais, prenez votre temps… Les Applicatives ne plaisantent pas. Control Applicative définit <*>, qui sait comment appliquer une fonction boxée à une valeur boxée !

applicative_just

i.e :

Utiliser <*> peut mener à des cas de figures intéressants, comme par exemple :

applicative_list

Voici quelque chose qu’on peut faire avec les Applicatives qu’il est impossible de faire avec les Functors. Comment appliquer une fonction qui prend deux arguments à deux valeurs boxées ?

Applicatives :

Les Applicatives poussent les Functors : « Les grands peuvent utiliser des fonctions avec n’importe quel nombre d’arguments. Armé de <$> et <*> je peux prendre n’importe quelle fonction qui attend un certain nombre de valeurs unboxée. Je peux aussi  ne passer que des valeurs boxés et en récupérer une nouvelle ! AHAHAHA ! »

Et il y a aussi une fonction liftA qui fait la même chose !

 Monads

Comment comprendre les Monads :

  1. Obtenir un doctorat en informatique
  2. Le balancer car il n’y en a pas besoin pour cette section.

Les Monads rajoutent un twist…. Les Functors appliquent une fonction à une valeur boxée :

fmap

 

Les Applicatives applique une fonction boxée à une valeur boxée.

applicative

Les Monads appliquent une fonction qui retourne une valeur boxée sur une valeur boxée. Pour se faire elles ont une fonction >>= (prononcé « bind »).

Voyons un exemple. Ce bon vieux Maybe est une Monad :

context

Juste une Monad, tranquille.

Imaginez half en tant que fonction ne prenant que des nombre pairs :

half

Que se passe-t-il lorsqu’on lui donne une valeur boxée ?

half_ouch

Nous avons besoin d’utiliser >>= pour fourrer la valeur boxée dans la fonction. Voici une photo de >>=

plunger

Voici comment ça marche :

Il se passe quoi concrètement ? Monad est une autre typeclass. Voici une définition partielle :

Où  >>= est :

bind_def

Donc Maybe est une Monad :

La voici en action avec un Just 3

monad_just

Et si l’on passe un Nothing ? C’est encore plus simple :

monad_nothing

 

On peut aussi chainer les appels :

monad_chain

whoa

 

C’est cool ! Donc maintenant nous savons que Maybe est un Functor, un Applicative et une Monad. Maintenant on va faire le tour d’un autre exemple : la Monad IO.

io

On a trois fonctions. getLine ne prend aucun argument et récupère l’entrée utilisateur :

getLine

readFile qui prend un string (nom du fichier) et retourne le contenu du fichier.

readFile

putStrLn prend un string et l’affiche.

putStrLn

Ces trois fonctions prennent une valeur classique (ou aucune valeur) et retourne une valeur boxée. On peut chainer ces opérations en utilisant >>=

monad_io

Oh ouais! Sièges au premier rang pour le Monad show !

Haskell nous fournit aussi un syntactic sugar pour les Monads, appelé notation do :

 Conclusion

  1. Un functor est un type qui implémente la typeclass Functor
  2. Un applicative est un type qui implémente la typeclass Applicative
  3. Une monad est un type qui implémente la typeclass Monad
  4. Un Maybe implémente les trois, donc c’est à la fois un functor, un applicative et une monad !

Quelle est la différence entre les trois ?

recap

  • functors : vous appliquez une fonction à une valeur boxée un utilisant fmap or <$>
  • applicatives : vous appliquez une fonction boxée à une valeur boxée avec <*> ou liftA
  • monads : vous appliquez une fonction qui retourne une valeur boxée à une valeur boxée, le tout en utilisant >>= ou liftM

Donc mon ami (à ce point là je pense que nous le sommes), je pense que nous sommes d’accord quand je dis que les monads sont à la fois simples et bien pensées ! Mais maintenant que tu as tiré le vin, il faut le boire :  pourquoi ne pas aller jeter un coup d’oeil à la section sur les Monads de LYAH ? Il y a beaucoup de choses dont je me suis inspiré là-bas car Miran aime aller dans les détails.

 

 

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

1 Comments

Laisser une réponse

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