OK, donc c'est vraiment pas la bonne forme pour répondre à votre question, mais je vais noter mes pensées dans le cas où il l'éclaire de quelqu'un d'autre. (J'en doute...)
Si une monade peut être considéré comme un "conteneur", puis les deux return
et join
ont assez évident de la sémantique. return
génère un 1-élément conteneur, et join
tourne un conteneur de conteneurs dans un seul conteneur. Rien de difficile à ce sujet.
Donc concentrons-nous sur les monades qui sont plus naturellement considérés comme des "actions". Dans ce cas, m x
est une action qui donne une valeur de type x
lorsque vous "exécuter" il. return x
n'a rien de spécial, et puis cède x
. fmap f
effectue une action qui donne un x
, et construit une action qui calcule x
, puis applique f
, et retourne le résultat. Pour l'instant, donc bon.
Il est assez évident que si l' f
génère lui-même une action, puis vous vous retrouvez avec est - m (m x)
. C'est, d'une action qui calcule une autre action. Dans un sens, c'est peut-être encore plus simple pour envelopper votre esprit autour de l' >>=
fonction qui prend une action et une "fonction qui produit une action" et ainsi de suite.
Donc, logiquement parlant, il semble join
permettrait de lancer la première action, de prendre les mesures qu'il produit, puis exécutez le. (Ou plutôt, join
serait de retour une action qui fait ce que je viens de décrire, si vous voulez couper les cheveux.)
Qui semble être l'idée centrale. Pour mettre en oeuvre join
, vous souhaitez exécuter une action, qui vous donne une autre action, et que vous exécutez. (Que ce soit "exécuter" arrive à dire pour cette monade.)
Compte tenu de cette idée, je peux prendre un coup de couteau à l'écriture de quelques join
implémentations:
join Nothing = Nothing
join (Just mx) = mx
Si l'action extérieure est Nothing
, rendement Nothing
, sinon retour à l'intérieur de l'action. Puis de nouveau, Maybe
est plus d'un contenant qu'une action, donc, nous allons essayer quelque chose d'autre...
newtype Reader s x = Reader (s -> x)
join (Reader f) = Reader (\ s -> let Reader g = f s in g s)
C'était... indolore. Un Reader
est vraiment juste une fonction qui prend un état global, et seulement alors, retourne son résultat. Donc, pour dépiler, vous appliquez l'état global de l'action extérieure, qui retourne un nouveau Reader
. Vous pouvez ensuite appliquer l'état pour cette fonction interne ainsi.
Dans un sens, c'est peut-être plus facile que de la manière habituelle:
Reader f >>= g = Reader (\ s -> let x = f s in g x)
Maintenant, qui est le lecteur de fonction, et qui est la fonction qui calcule le prochain lecteur...?
Maintenant, nous allons essayer la bonne vieille State
monade. Ici, chaque fonction prend un état initial en entrée mais aussi renvoie un nouvel état avec sa sortie.
data State s x = State (s -> (s, x))
join (State f) = State (\ s0 -> let (s1, State g) = f s0 in g s1)
Ce n'était pas trop dur. Il s'agit essentiellement d'exécution, suivi de la course.
Je vais arrêter de taper maintenant. Hésitez pas à signaler tous les défauts et les fautes de frappe dans mes exemples... :-/