Différence entre la Monade et Applicative en Haskell
Je viens de lire la suite de typeclassopedia à propos de la différence entre Monad
et Applicative
. Je peux comprendre qu'il n'est pas join
dans Applicative
. Mais la description suivante semble vague pour moi et je ne pouvais pas comprendre ce que signifie exactement "le résultat" d'un monadique de calcul/de l'action. Donc, si je mets une valeur dans Maybe
, ce qui rend une monade, quel est le résultat de ce "calcul"?
Regardons de plus près le type de (>>=). L'intuition est
qu'il combine les deux calculs en un seul calcul. L'
premier argument, m, est le premier calcul. Cependant, il serait
ennuyeux si le deuxième argument était juste une m b, alors il n'y aurait pas
moyen pour les calculs d'interagir les uns avec les autres (en fait, ce
est exactement la situation avec Applicative). Ainsi, le deuxième argument de
(>>=) a type a -> m, b: une fonction de ce type, compte tenu d'un résultat de
le premier calcul, peut produire un second calcul à exécuter.
... De manière intuitive, c'est cette capacité à utiliser la sortie de la précédente
les calculs de décider quels calculs à la prochaine exécution qui rend la Monade
plus puissant que Applicative. La structure d'un Applicatif
le calcul est fixe, alors que la structure d'une Monade calcul peut
changement basé sur les résultats intermédiaires.
Est-il un exemple concret pour illustrer "la capacité d'utiliser la sortie à partir des calculs précédents pour décider quels calculs à la prochaine exécution", qui Applicatifs n'ont pas?
Just 1
décrit un "calcul", dont le "résultat" est 1.Nothing
décrit un calcul qui ne produit pas de résultats.- Voir aussi arrowdodgerquestion "Quel avantage ne Monade nous donnent sur un Applicatif?", qui a quelques bonnes réponses (full disclosure: dont un des miens).
Vous devez vous connecter pour publier un commentaire.
Mon exemple préféré est le "purement applicative Soit". Nous allons commencer par l'analyse de la base de Monade instance, Soit
Cette instance intègre un très naturelle de court-circuit notion: on procède de gauche à droite et une fois un calcul unique "échoue" dans le
Left
puis tout le reste à faire ainsi. Il y a aussi le naturelApplicative
exemple que toutMonad
aoù
ap
n'est rien de plus que de gauche à droite et de séquençage de l'avant unreturn
:Maintenant, le problème avec cette
Either
instance vient à la lumière lorsque vous souhaitez collecter les messages d'erreur qui se produisent n'importe où dans un calcul et d'une certaine façon de produire un résumé des erreurs. Cela va à l'encontre de court-circuit. Il vole également dans le visage du type de(>>=)
Si nous pensons à
m a
que "le passé" etm b
comme "l'avenir" alors(>>=)
produit de l'avenir à partir du passé, tant qu'il peut exécuter le "pas à pas"(a -> m b)
. Ce "pas à pas" exige que la valeur dea
existe vraiment dans le futur... et c'est impossible pourEither
. Donc(>>=)
demandes de court-circuit.Donc, au lieu de cela, nous allons mettre en œuvre une
Applicative
instance qui ne peut pas avoir un correspondantMonad
.Désormais la mise en œuvre de
(<*>)
est la partie la peine d'examiner attentivement. Il effectue une certaine quantité de "court-circuit" dans sa première 3 cas, mais quelque chose d'intéressant, dans la quatrième.Nouveau, remarquez que si on pense à la gauche de l'argument que "le passé" et le droit de l'argument de "l'avenir" alors
(<*>)
est spécial par rapport à(>>=)
que c'est permis "d'ouvrir" l'avenir et le passé, en parallèle au lieu de nécessairement besoin de résultats à partir de "le passé" afin de calculer "l'avenir".Cela signifie, directement, que nous pouvons utiliser notre purement
Applicative
Either
de recueillir des erreurs, ignorantRight
s si toutLeft
s existent dans la chaîneDonc, nous allons retourner cette intuition sur sa tête. Que pouvons-nous pas faire avec un purement applicative
Either
? Eh bien, puisque son fonctionnement dépend de l'examen de l'avenir avant l'exécution du passé, nous devons être en mesure de déterminer la structure de l'avenir sans selon les valeurs du passé. En d'autres termes, on ne peut pas écrirequi satisfait les équations suivantes
alors que nous pouvons écrire
ifM
tels que
Cette impossibilité résulte de ce que
ifA
incarne exactement l'idée du résultat du calcul en fonction des valeurs ancrées dans l'argument des calculs.ifA t c a = g <$> t <*> c <*> a where g b x y = if b then x else y
?ifA (Just True) (Just ()) Nothing == Nothing
, alors queifM (Just True) (Just ()) Nothing == Just ()
. Il serait probablement plus exact de dire "on ne peut pas écrireifA
avec les attendus de la sémantique".ifX
n'est pas un bon véhicule pour explorer cette question.ifM
est exactement le véhicule de l'examen de la puissance de la monade, mais il suppose que lesifM
est la sémantique de ne pasif' <$> a <*> b <*> c where if' b t e = if b then t else e
. Le vrai défi, c'est quand les effets d'un amalgame avec des valeurs.ifM
est en effet l'essence de la Monade distinction. Mais pour Applicative,ifA
est rien de spécial, leif
qui se passe à l'intérieur encore... les signatures ne sont que superficiellement similaire (OIE c'est source de confusion :)).ifA
- delà de son type.Monad
instance définie, sesApplicative
instance de doit est compatible avec celleMonad
instance (pure = return
, (<*>) = ap). While the second
Applicative` instance de définition dans cette réponse satisfait leApplicative
lois, il viole cette documenté exigence. La bonne façon d'obtenir cette deuxièmeApplicative
instance est de définir un autre type qui est isomorphe àEither
.Errors
type deControl.Applicative.Lift
met en œuvre précisément la "rassembler toutes les" erreurs de comportement décrit dans cette réponse.Either
code ici l'hypothèse que nous avons abandonné lebase
Monad
instance. J'ai aussi fait de ne pas savoir à propos deLift (Constant e)
. C'est merveilleux!newtype
-ing-il.Just 1
décrit un "calcul", dont le "résultat" est 1.Nothing
décrit un calcul qui ne produit pas de résultats.La différence entre la Monade et un Applicative est que dans la Monade, il y a un choix. La clé de la distinction des Monades est la possibilité de choisir entre différents chemins dans le calcul (pas juste sortir début). En fonction de la valeur produite par l'étape précédente dans le calcul, le reste de calcul de la structure peut changer.
Voici ce que cela signifie. Dans le monadique de la chaîne d'
la
if
choisit ce calcul à construire.Dans le cas de l'Applicatif, dans
toutes les fonctions de travail "à l'intérieur" des calculs, il n'y a aucune chance de briser une chaîne. Chaque fonction se transforme tout simplement une valeur c'est de la fed. La "forme" du calcul de la structure est entièrement "à l'extérieur" de l'fonctions de point de vue.
Une fonction peut retourner une valeur spéciale pour indiquer l'échec, mais il ne peut pas causer prochaines étapes du calcul pour être ignorée. Ils sont tous à traiter de la valeur spéciale en particulier. La forme du calcul ne peut pas être changé selon la valeur reçue.
Avec les monades, les fonctions elles-mêmes de construire des calculs de leur choix.
do-block
notation.a <*> b <*> ...
) sont connus d'avance, mais avec Monadique combinaison (a >>= (\ ... -> b >>= ... )
) de chaque côté de calcul est calculée ("est dépendante") sur la valeur produite par le calcul précédent. il y a deux échéances, deux mondes: un pur où le calcul des descriptions (a
,b
...) sont créés et combinés, et potentiellement impure où ils sont "exécuter" - où les calculs se produire.Voici mon point de vue sur @J. Abrahamson est pourquoi
ifA
ne peut pas utiliser la valeur à l'intérieur par exemple(pure True)
. En substance, il encore se résume à l'absence de lajoin
fonction deMonad
dansApplicative
, qui unifie les deux points de vue différents donnée dans typeclassopedia pour expliquer la différence entreMonad
etApplicative
.Donc, à l'aide de @J. Abrahamson exemple purement applicative
Either
:(qui a même court-circuiter l'effet de la
Either
Monad
), et leifA
fonctionQue faire si nous essayons d'atteindre le mentionné équations:
?
Bien, comme l'a déjà souligné, en fin de compte, le contenu de
(pure True)
, ne peut pas être utilisé par un plus tard calcul. Mais techniquement parlant, ce n'est pas le bon. Nous peut utiliser le contenu de(pure True)
depuis unMonad
est aussi unFunctor
avecfmap
. Nous pouvons faire:Le problème est avec le type de retour de
ifA'
, qui estf (f a)
. DansApplicative
, il n'y a aucun moyen de s'effondrer imbriquéesApplicative
S en un seul. Mais cet effondrement de la fonction est précisément ce quejoin
dansMonad
effectue. Donc,de satisfaire les équations pour
ifA
, si nous pouvons mettre en œuvrejoin
de façon appropriée. CeApplicative
qui manque ici est exactement lejoin
fonction. En d'autres termes, nous pouvons en quelque sorte le résultat de la précédente résultat dansApplicative
. Mais le faire dans unApplicative
cadre impliquera augmentant le type de la valeur de retour d'une imbriqués applicative de valeur, qui nous ont aucun moyen de les ramener à un seul niveau applicatif valeur. Ce sera un sérieux problème parce que, par exemple, on ne peut pas composer des fonctions à l'aide deApplicative
S de façon appropriée. À l'aide dejoin
résout le problème, mais bien l'introduction dejoin
favorise laApplicative
à unMonad
.join Right (Right a) = Right a; join Right (Left e) = Left e; join Left (Left e) = Left e
mais ce n'est pas bon:join Left (Right a) =? Left (Right a)
?join
'argument deResult<Result<_,_>,Result<_,_>>
, ou pire.La clé de la différence peut être observée dans le type de
ap
vs type de=<<
.Dans les deux cas, il est
m a
, mais seulement dans le second casm a
peut décider si la fonction(a->m b)
est appliquée. À son tour, la fonction(a->m b)
peut "décider" si la fonction est lié à côté qui est appliquée par la production d'un telm b
qui ne "comprend" pasb
(comme[]
,Nothing
ouLeft
).Dans
Applicative
il n'existe aucun moyen pour les fonctions "à l'intérieur"m (a->b)
à faire de ces "décisions" - ils toujours de produire une valeur de typeb
.Dans
Applicative
ce n'est pas possible, donc ne peut pas montrer un exemple. Le plus proche est:Bien, que le flou est un peu délibérée, parce que ce "résultat" est d'une monadique de calcul est quelque chose qui dépend de chaque type. La meilleure réponse est un peu tautologique: le "résultat" (ou résultats, car il peut y avoir plus d'un) est n'importe quelle valeur(s) de l'instance de mise en œuvre de
(>>=) :: Monad m => m a -> (a -> m b) -> m b
invoque l'argument de fonction avec.La
Maybe
monade ressemble à ceci:La seule chose ici qui se qualifie comme un "résultat" est le
a
dans la seconde équation pour>>=
, parce que c'est la seule chose qui n'est jamais "nourris" au deuxième argument de>>=
.D'autres réponses ont disparu dans la profondeur sur la
ifA
vsifM
différence, donc, j'ai pensé mettre en évidence une autre différence importante: applicatifs composer, les monades ne sont pas. AvecMonad
s, si vous voulez faire unMonad
qui combine les effets de deux existantes, vous devez réécrire l'un d'entre eux comme une monade transformateur. En revanche, si vous avez deuxApplicatives
vous pouvez facilement faire de plus en plus complexes, l'un d'entre eux, comme illustré ci-dessous. (Le Code est copypasted detransformateurs
.)Maintenant, si nous ajoutons dans la
Constant
foncteur/applicative:...on peut assembler le "applicative
Either
" de l'autre des réponses deLift
etConstant
:Je tiens à partager mon point de vue sur ce "terrible miffy" chose, comme je le comprends tout à l'intérieur du contexte appliquée, ainsi par exemple:
upp doit être Juste en "Vrai" ... mais
(le "bon" choix se fait à l'intérieur du contexte)
J'ai expliqué cela à moi-même de cette manière, juste avant la fin du calcul dans le cas où >>1 nous obtenons quelque chose comme ça dans la "chaîne" :
qui, selon, par définition, des Applicatifs est évalué comme:
qui quand "quelque chose" est Rien ne devient Rien selon Foncteur contrainte (fmap plus Rien ne donne Rien). Et il n'est pas possible de définir un Foncteur avec "fmap f Rien = quelque chose de" fin de l'histoire.