Je me retrouve souvent à écrire d'abord une action monadique en notation do
, puis à la refactoriser en une simple expression monadique (ou functorielle). Cela se produit surtout quand le bloc do
s'avère plus court que prévu. Parfois, je refactorise dans l'autre sens; cela dépend du code en question.
Ma règle générale est la suivante : si le bloc do
ne comporte que quelques lignes, il est généralement plus propre sous forme d'expression courte. Un long bloc do
est probablement plus lisible tel quel, à moins que vous ne trouviez un moyen de le diviser en fonctions plus petites et plus composables.
À titre d'exemple concret, voici comment nous pourrions transformer votre extrait de code verbeux en une version plus simple.
main = do
strFile <- readFile "testfile.txt"
let analysisResult = stringAnalyzer strFile
return analysisResult
Tout d'abord, remarquez que les deux dernières lignes ont la forme let x = y in return x
. Cela peut bien sûr être transformé en simplement return y
.
main = do
strFile <- readFile "testfile.txt`
return (stringAnalyzer strFile)
Il s'agit d'un bloc do
très court : nous associons readFile "testfile.txt"
à un nom, puis faisons quelque chose avec ce nom à la ligne suivante. Essayons de le 'désucrer' comme le ferait le compilateur :
main = readFile "testFile.txt" >>= \strFile -> return (stringAnalyser strFile)
Regardez la forme lambda du côté droit de >>=
. Il réclame d'être réécrit en style point-free : \x -> f $ g x
devient \x -> (f . g) x
qui devient f . g
.
main = readFile "testFile.txt" >>= (return . stringAnalyser)
C'est déjà beaucoup plus net que le bloc do
original, mais nous pouvons aller plus loin.
Voici la seule étape qui demande un peu de réflexion (mais une fois familiarisé avec les monades et les foncteurs, cela devrait être évident). La fonction ci-dessus est évocatrice de l'une des lois des monades : (m >>= return) == m
. La seule différence est que la fonction du côté droit de >>=
n'est pas seulement return
- nous faisons quelque chose à l'objet à l'intérieur de la monade avant de le renvoyer dans un return
. Mais le motif de 'faire quelque chose à une valeur enveloppée sans affecter son enveloppe' correspond exactement à ce que Functor
est censé faire. Toutes les monades sont des foncteurs, nous pouvons donc refactoriser cela pour ne même pas avoir besoin de l'instance Monad
:
main = fmap stringAnalyser (readFile "testFile.txt")
Enfin, notez que <$>
est juste une autre façon d'écrire fmap
.
main = stringAnalyser <$> readFile "testFile.txt"
Je pense que cette version est beaucoup plus claire que le code original. Il peut être lu comme une phrase : "main
est stringAnalyser
appliqué au résultat de la lecture de "testFile.txt"
". La version originale vous embourbe dans les détails procéduraux de son fonctionnement.
Addendum : mon commentaire selon lequel 'toutes les monades sont des foncteurs' peut en fait être justifié par l'observation que m >>= (return . f)
(alias liftM
de la bibliothèque standard) est équivalent à fmap f m
. Si vous avez une instance de Monad
, vous obtenez gratuitement une instance de Functor
- il vous suffit de définir fmap = liftM
! Si quelqu'un a défini une instance de Monad
pour son type mais n'a pas défini d'instances pour Functor
et Applicative
, je considérerais cela comme un bogue. Les clients s'attendent à pouvoir utiliser les méthodes de Functor
sur les instances de Monad
sans trop de tracas.
0 votes
Q2 : Les tutoriels doivent-ils enseigner l'IO avec
do
parce que (a) l'applicative est plus récente et moins connue, (b) la notation do est plus proche du code impératif, que beaucoup d'étudiants apprennent en premier, et (c) il y a certaines choses qu'on peut faire avec un Monad qu'on ne peut pas faire avec une applicative, principalement changer quel code est appelé en fonction de la valeur précédemment sortie.0 votes
Q1: Applicative est agréable, propre et très fonctionnel dans le style. Maîtrisez-le et soyez à l'affût du plus grand nombre d'opportunités possibles pour nettoyer votre code avec un peu de
*>
, mais vous devrez parfois utiliser toute la puissance des monades et lorsque vous le ferez, la notationdo
est généralement la plus claire.0 votes
Utilisez-le seulement lorsque cela facilite les choses. Par exemple, n'utilisez jamais la notation "do" pour une seule ligne. (Comme, ne faites jamais
main = do print "Hello World"
. Utilisez plutôtmain = print "Hello World"
.) Assurez-vous de bien comprendre les monades en termes de>>=
etreturn
. Lorsque vous avez bien compris cela, vous pouvez utiliserdo
.1 votes
Le notation do est le langage naturel pour travailler dans la catégorie de Kleisli