Haskell Monade lier opérateur de confusion
Ok, donc je ne suis pas un programmeur Haskell, mais je suis absolument intrigué par beaucoup d'idées derrière Haskell et je cherche à l'apprendre. Mais je suis coincé à la case départ: je n'arrive pas à m'enrouler autour de monades, qui semblent être assez fondamentales. Je sais qu'il y a un million de questions sur SO demandant d'expliquer les monades, donc je vais être un peu plus précis sur ce qui me dérange:
J'ai lu cet excellent article ( une introduction en Javascript), et j'ai pensé que je compris monades complètement. Ensuite, j'ai lu L'entrée Wikipedia sur les monades, et j'ai vu ceci:
Une opération de liaison de type polymorphe (M t)→(t→M u)→(M u), que Haskell représente par l'opérateur infixe>>=. Son premier argument est une valeur dans un type monadique, son second argument est une fonction qui cartes du type sous-jacent du premier argument à un autre type monadique, et son résultat est dans cet autre type monadique.
OK, dans l'article que j'ai cité, lier était une fonction qui a pris un seul argument . Wikipédia dit que deux. Ce que j'ai pensé {[14] } que j'ai compris au sujet des monades était le suivant:
- le but D'une monade est de prendre une fonction avec différents types d'entrée et de sortie et de la rendre composable. Il le fait en enveloppant les types d'entrée et de sortie avec un seul type monadique.
- une monade se compose de deux fonctions interdépendantes: bind et unit. Bind prend une fonction non composable f et renvoie une nouvelle fonction g qui accepte le type monadique en entrée et renvoie le type monadique. g est modulable. La fonction unit prend un argument du type attendu par f et l'enveloppe dans le type monadique. Ceci peut ensuite être passé à g, ou à n'importe quelle composition de fonctions comme g.
Mais il doit y avoir quelque chose de mal, car mon concept de bind prend un argument: une fonction. Mais (selon Wikipedia) la liaison de Haskell prend en fait deux arguments! Où est mon erreur?
3 réponses
Vous ne faites pas d'erreur. L'idée clé à comprendre ici est currying-qu'une fonction Haskell de deux arguments peut être vue de deux façons. Le premier est aussi simplement une fonction de deux arguments. Si vous avez, par exemple, (+)
, cela est généralement considéré comme prenant deux arguments et les ajoutant. L'autre façon de le voir est en tant que producteur de machine d'addition. (+)
est une fonction qui prend un nombre, dire x
, et fait une fonction qui va ajouter x
.
(+) x = \y -> x + y
(+) x y = (\y -> x + y) y = x + y
Lorsqu'il s'agit de monades, parfois, il est probablement préférable, comme éphémère mentionné ci-dessus, de penser à =<<
, la version retournée de >>=
. Il y a deux façons de regarder ceci:
(=<<) :: (a -> m b) -> m a -> m b
Qui est une fonction de deux arguments, et
(=<<) :: (a -> m b) -> (m a -> m b)
Qui transforme la fonction d'entrée en une version facilement composée comme l'article mentionné. Ceux-ci sont équivalents tout comme (+)
comme je l'ai expliqué précédemment.
Permettez-moi de détruire vos croyances sur les monades. J'espère sincèrement que vous réalisez que je n'essaie pas d'être grossier; j'essaie simplement d'éviter de hacher des mots.
Le but D'une monade est de prendre une fonction avec différents types d'entrée et de sortie et de la rendre composable. Il le fait en enveloppant les types d'entrée et de sortie avec un seul type monadique.
Pas exactement. Lorsque vous commencez une phrase avec "le but d'une monade", vous êtes déjà sur le mauvais pied. Les monades n'avez pas nécessairement un "but". Monad
est simplement une abstraction, une classification qui s'applique à certains types et pas à d'autres. Le but de l'abstraction Monad
est simplement cela, l'abstraction.
Une monade se compose de deux fonctions interdépendantes: bind et unit.
Oui et non. La combinaison de bind
et unit
sont suffisantes pour définir une Monade, mais la combinaison de join
, fmap
, et unit
sont également suffisantes. Ce dernier est, en fait, la façon dont Les monades sont généralement décrites dans la théorie des catégories.
Bind prend une fonction non composable f et renvoie une nouvelle fonction g qui accepte le type monadique en entrée et renvoie le type monadique.
Encore une fois, pas exactement. Une fonction monadique f :: a -> m b
est parfaitement composable, avec certains types. Je peux poster-composer avec une fonction de g :: m b -> c
pour obtenir g . f :: a -> c
, ou je peux pré-composer avec une fonction de h :: c -> a
pour obtenir f . h :: c -> m b
.
Mais vous avez la deuxième partie absolument droit: (>>= f) :: m a -> m b
. Comme d'autres l'ont noté, la fonction bind
de Haskell prend les arguments dans l'ordre opposé.
G est modulable.
Eh bien, oui. If g :: m a -> m b
, alors vous pouvez pré-composer avec une fonction de f :: c -> m a
pour obtenir g . f :: c -> m b
, ou vous pouvez poster-composer avec une fonction de h :: m b -> c
pour obtenir h . g :: m a -> c
. Notez que c
pourrait - est de la forme m v
où m
est une Monade. Je suppose que quand vous dites "modulable" tu veux dire "vous pouvez composer arbitrairement longues chaînes de fonctions de cette forme", ce qui est en quelque sorte vrai.
La fonction unit prend un argument du type attendu par f et l'enveloppe dans le type monadique.
Une façon détournée de le dire, Mais oui, c'est à peu près juste.
Ceci[le résultat de l'application de
unit
à une certaine valeur] peut alors être passé à g, ou à n'importe quelle composition de fonctions comme g.
Encore une fois, oui. Bien qu'il ne soit généralement pas idiomatique Haskell d'appeler unit
(ou dans Haskell, return
), puis passez-le à (>>= f)
.
-- instead of
return x >>= f >>= g
-- simply go with
f x >>= g
-- instead of
\x -> return x >>= f >>= g
-- simply go with
f >=> g
-- or
g <=< f
L'article que vous liez est basé sur l'article de sigfpe, qui utilise une définition inversée de bind:
La première chose est que j'ai renversé la définition de
bind
et l'ai écrit comme le mot 'bind' alors qu'il est normalement écrit comme l'opérateur>>=
. Donc {[9] } est normalement écrit commex >>= f
.
Ainsi, le Haskell bind
prend une valeur comprise dans une monade, et retourne une fonction qui prend une fonction et l'appelle avec la valeur extraite. J'ai peut-être l'aide terminologie non précise, alors peut-être mieux avec le code.
Vous avez:
sine x = (sin x, "sine was called.")
cube x = (x * x * x, "cube was called.")
Maintenant, traduire votre bind JS (Haskell fait du currying automatique, donc appeler bind f
renvoie une fonction qui prend un tuple, puis la correspondance de modèle prend soin de la décompresser dans x
et s
, j'espère que c'est compréhensible):
bind f (x, s) = (y, s ++ t)
where (y, t) = f x
Vous pouvez le voir fonctionner:
*Main> :t sine
sine :: Floating t => t -> (t, [Char])
*Main> :t bind sine
bind sine :: Floating t1 => (t1, [Char]) -> (t1, [Char])
*Main> (bind sine . bind cube) (3, "")
(0.956375928404503,"cube was called.sine was called.")
Maintenant, inversons les arguments de bind
:
bind' (x, s) f = (y, s ++ t)
where (y, t) = f x
Vous pouvez clairement voir qu'il fait toujours la même chose chose, mais avec une syntaxe un peu différente:
*Main> bind' (bind' (3, "") cube) sine
(0.956375928404503,"cube was called.sine was called.")
Maintenant, Haskell a une astuce de syntaxe qui vous permet d'utiliser n'importe quelle fonction en tant qu'opérateur d'infix. Donc, vous pouvez écrire:
*Main> (3, "") `bind'` cube `bind'` sine
(0.956375928404503,"cube was called.sine was called.")
Maintenant renommer bind'
pour >>=
((3, "") >>= cube >>= sine
) et vous avez ce que vous cherchez. Comme vous pouvez le voir, avec cette définition, vous pouvez se débarrasser efficacement de la composition de l'opérateur.
Traduire la nouvelle chose en JavaScript donnerait quelque chose comme ceci (notez que encore une fois, je ne fais que Inverser l'ordre des arguments):
var bind = function(tuple) {
return function(f) {
var x = tuple[0],
s = tuple[1],
fx = f(x),
y = fx[0],
t = fx[1];
return [y, s + t];
};
};
// ugly, but it's JS, after all
var f = function(x) { return bind(bind(x)(cube))(sine); }
f([3, ""]); // [0.956375928404503, "cube was called.sine was called."]
J'espère que cela aide, et n'introduit pas plus de confusion - le fait est que ces deux définitions de liaison sont équivalentes, ne différant que par la syntaxe des appels.