Distinguons d'abord entre l'apprentissage des concepts abstraits et l'apprentissage d'exemples spécifiques d'entre eux.
Vous n'irez pas très loin en ignorant tous les exemples spécifiques, pour la simple raison qu'ils sont tout à fait omniprésents. En fait, les abstractions existent en grande partie parce qu'elles unifient les choses que vous feriez de toute façon avec les exemples spécifiques.
Les abstractions elles-mêmes, en revanche, sont certainement utiles , mais elles ne sont pas immédiatement nécessaires. Vous pouvez obtenir assez loin en ignorant complètement les abstractions et en utilisant directement les différents types. Vous voudrez éventuellement les comprendre, mais vous pourrez toujours y revenir plus tard. En fait, je peux presque garantir que si vous faites cela, lorsque vous y reviendrez, vous vous claquerez le front et vous vous demanderez pourquoi vous avez passé tout ce temps à faire les choses à la dure au lieu d'utiliser les outils polyvalents pratiques.
Prenons Maybe a
l'exemple. C'est juste un type de données:
data Maybe a = Just a | Nothing
C'est tout sauf auto-documenté; c'est une valeur facultative. Soit vous avez "juste" quelque chose de type a
, soit vous n'avez rien. Supposons que vous ayez une fonction de recherche quelconque, qui revient Maybe String
à représenter la recherche d'une String
valeur qui peut ne pas être présente. Donc, votre motif correspond à la valeur pour voir de quelle valeur il s'agit:
case lookupFunc key of
Just val -> ...
Nothing -> ...
C'est tout!
Vraiment, vous n'avez besoin de rien d'autre. Pas de Functor
s ou Monad
s ou autre chose. Ceux-ci expriment des façons courantes d'utiliser des Maybe a
valeurs ... mais ce ne sont que des idiomes, des "modèles de conception", peu importe comment vous pouvez l'appeler.
Le seul endroit où vous ne pouvez vraiment pas l'éviter complètement est IO
, mais c'est une mystérieuse boîte noire de toute façon, donc cela ne vaut pas la peine d'essayer de comprendre ce que cela signifie en tant que Monad
ou quoi que ce soit.
En fait, voici une feuille de triche pour tout ce que vous devez vraiment savoir IO
pour l'instant:
Si quelque chose a un type IO a
, cela signifie que c'est une procédure qui fait quelque chose et crache une a
valeur.
Lorsque vous avez un bloc de code utilisant la do
notation, écrivez quelque chose comme ceci:
do -- ...
inp <- getLine
-- etc...
... signifie exécuter la procédure à droite de <-
et affecter le résultat au nom à gauche.
Alors que si vous avez quelque chose comme ça:
do -- ...
let x = [foo, bar]
-- etc...
... cela signifie affecter la valeur de l'expression simple (pas une procédure) à droite de la =
au nom à gauche.
Si vous y mettez quelque chose sans assigner de valeur, comme ceci:
do putStrLn "blah blah, fishcakes"
... cela signifie exécuter une procédure et ignorer tout ce qu'elle retourne. Certaines procédures ont le type IO ()
- le ()
type est une sorte d'espace réservé qui ne dit rien, ce qui signifie simplement que la procédure fait quelque chose et ne renvoie pas de valeur. Un peu comme une void
fonction dans d'autres langues.
L'exécution de la même procédure plusieurs fois peut donner des résultats différents; c'est un peu l'idée. C'est pourquoi il n'y a aucun moyen de "supprimer" IO
une valeur, car quelque chose IO
n'est pas une valeur, c'est une procédure pour obtenir une valeur.
La dernière ligne d'un do
bloc doit être une procédure simple sans affectation, où la valeur de retour de cette procédure devient la valeur de retour pour le bloc entier. Si vous souhaitez que la valeur de retour utilise une valeur déjà affectée, la return
fonction prend une valeur simple et vous donne une procédure sans opération qui renvoie cette valeur.
À part cela, il n'y a rien de spécial IO
; ces procédures sont en fait des valeurs simples et vous pouvez les transmettre et les combiner de différentes manières. Ce n'est que lorsqu'ils sont exécutés dans un do
bloc appelé quelque part main
qu'ils font n'importe quoi.
Donc, dans quelque chose comme ce programme d'exemple stéréotypé totalement ennuyeux:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... vous pouvez le lire comme un programme impératif. Nous définissons une procédure nommée hello
. Une fois exécuté, il exécute d'abord une procédure pour imprimer un message vous demandant votre nom; ensuite, il exécute une procédure qui lit une ligne d'entrée et affecte le résultat à name
; puis il attribue une expression au nom msg
; puis il imprime le message; puis il renvoie le nom de l'utilisateur comme résultat de l'ensemble du bloc. Puisque name
est un String
, cela signifie que hello
c'est une procédure qui renvoie un String
, donc il a du type IO String
. Et maintenant, vous pouvez exécuter cette procédure ailleurs, tout comme elle s'exécute getLine
.
Pfff, monades. Qui en a besoin?