La première chose à comprendre au sujet de la continuation de la monade, c'est que, fondamentalement, il n'est pas vraiment faire quoi que ce soit. C'est vrai!
L'idée de base d'une poursuite en général, c'est qu'il représente le reste d'un calcul. Disons que nous avons une expression comme ceci: foo (bar x y) z
. Maintenant, extraire juste la partie entre parenthèses, bar x y
--c'est la partie de l'expression total, mais ce n'est pas seulement une fonction, nous pouvons l'appliquer. Au lieu de cela, c'est quelque chose que nous avons besoin d'appliquer une fonction à l'. Ainsi, nous pouvons parler du "reste du calcul" dans ce cas, comme \a -> foo a z
, ce qui, nous pouvons l'appliquer à l' bar x y
de reconstruire la forme complète.
Maintenant, il se trouve que cette notion de "le reste du calcul" est utile, mais il est maladroit, car c'est quelque chose en dehors de la sous-expression nous intéresse. Pour améliorer les choses, nous pouvons nous tourner les choses à l'intérieur: extrait de la sous-expression qui nous intéresse, puis les envelopper dans une fonction qui prend un argument représentant le reste du calcul: \k -> k (bar x y)
.
Cette version modifiée nous donne beaucoup de souplesse, non seulement il extrait une sous-expression de son contexte, mais il nous permet de le manipuler extérieure contexte au sein de la sous-expression elle-même. Nous pouvons penser à cela comme une sorte de suspension du calcul, nous donnant un contrôle explicite sur ce qui se passe ensuite. Maintenant, comment pourrait-on généraliser? Ainsi, la sous-expression est à peu près inchangé, il faut juste laisser le remplacer par un paramètre à l'intérieur de la fonction, en nous donnant des \x k -> k x
--en d'autres termes, rien de plus que la fonction de demande, inversé. On pourrait tout aussi bien écrire flip ($)
, ou ajouter un peu d'exotisme en langue étrangère de la saveur et de le définir comme un opérateur |>
.
Maintenant, il serait simple, mais fastidieux et horriblement abrutissant, à traduire chaque pièce d'une expression de cette forme. Heureusement, il ya une meilleure façon. Comme Haskell programmeurs, quand on pense à la construction d'un calcul dans un contexte de fond la prochaine chose que nous pensons est-à - dire, est-ce une monade? Et dans ce cas, la réponse est oui, oui, il est.
Transformer cela en une monade, nous commençons avec deux blocs de construction de base:
- Pour une monade
m
, une valeur de type m a
représente d'avoir accès à une valeur de type a
dans le cadre de la monade.
- Le cœur de notre "suspendu calculs" est renversé en fonction de l'application.
Que signifie le fait d'avoir accès à quelque chose de type a
dans ce contexte? Cela signifie simplement que, pour une certaine valeur en x :: a
, nous avons appliqué flip ($)
de x
, nous donnant une fonction qui prend une fonction qui prend un argument de type a
, et applique cette fonction à l' x
. Disons que nous avons une suspension du calcul de portefeuille d'une valeur de type Bool
. De quel type est-ce à nous donner?
> :t flip ($) True
flip ($) True :: (Bool -> b) -> b
Donc, pour reprendre les calculs, le type m a
de (a -> b) -> b
... ce qui est peut-être une déception, car on savait déjà que la signature d' Cont
, mais l'humour moi pour l'instant.
Une chose intéressante à noter ici est qu'une sorte de "renversement" s'applique également à la monade type: Cont b a
représente une fonction qui prend une fonction a -> b
et évalue b
. Comme une continuation représente "l'avenir" d'un calcul, de sorte que le type a
à la signature représente dans un certain sens, "le passé".
Donc, en remplaçant (a -> b) -> b
avec Cont b a
, quel est le type monadique de notre bloc de construction de base de la fonction d'inversion d'application? a -> (a -> b) -> b
se traduit par a -> Cont b a
... le même type de signature en tant que return
et, en fait, c'est exactement ce qu'il est.
À partir d'ici, à peu près tout tombe directement dans les types: Il y a essentiellement pas de meilleure façon de mettre en oeuvre >>=
en plus de la mise en œuvre effective. Mais qu'en est-il réellement le faire?
À ce stade, nous en revenons à ce que j'ai dit d'abord: la continuation de la monade n'est pas vraiment faire beaucoup de chose. Quelque chose de type Cont r a
est trivialement équivalent à quelque chose de juste taper a
, simplement en fournissant id
comme argument de la suspension du calcul. Cela pourrait conduire à se demander si, si Cont r a
est une monade, mais la conversion est si trivial, ne devriez - a
seul aussi être une monade? Bien sûr, cela ne fonctionne pas comme il est, car il n'y a pas de constructeur de type à définir en tant que Monad
de l'instance, mais dis-nous ajouter une mince enveloppe, comme data Id a = Id a
. C'est en effet une monade, à savoir l'identité de l'errance.
Qu'est - >>=
faire pour l'identité de l'errance? Le type de signature est - Id a -> (a -> Id b) -> Id b
, ce qui est équivalent à a -> (a -> b) -> b
, ce qui est tout simple fonction de l'application à nouveau. Ayant établi l' Cont r a
est trivialement équivalent à Id a
, nous pouvons en déduire que dans ce cas également, (>>=)
est juste en fonction de l'application.
Bien sûr, Cont r a
est un fou le monde à l'envers où tout le monde a des barbichettes, de sorte que ce qui se passe réellement consiste à brouiller les choses autour de confondre les moyens afin de chaîne de deux suspensions de calculs dans un nouveau suspendu le calcul, mais en substance, il n'est pas réellement quelque chose d'inhabituel se passait! L'application de fonctions à arguments, ho hum, une autre journée dans la vie d'un programmeur fonctionnel.