126 votes

Les applicatifs composent, les monades ne le font pas

Les applicatifs composent, les monades ne le font pas.

Que signifie l'affirmation ci-dessus ? Et quand l'un est-il préférable à l'autre ?

6 votes

D'où tenez-vous cette affirmation ? Il pourrait être utile de voir le contexte.

0 votes

@FUZxxl : Je l'ai entendu à plusieurs reprises de la part de nombreuses personnes différentes, récemment de la part de debasishg sur twitter.

0 votes

Concernant la deuxième question. Il n'y a pas beaucoup de structures générales qui sont des applicatifs mais pas des monades. L'applicatif à erreurs multiples dans l'article de Conor McBride et Ross Paterson en est une. Il existe quelques structures spécifiques qui sont des applicatifs et non des monades et qui peuvent être optimisées si elles ne sont que des applicatifs - les combinateurs d'analyseurs de Doaiste Swierstra en sont un, et les combinateurs de Chalkboard en sont un. Active un autre type.

125voto

pigworker Points 20324

Si nous comparons les types

(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m =>       m s -> (s -> m t) -> m t

nous avons un indice de ce qui sépare les deux concepts. Ce (s -> m t) dans le type de (>>=) montre qu'une valeur dans s peut déterminer le comportement d'un calcul dans m t . Les monades permettent l'interférence entre les couches de valeur et de calcul. Le site (<*>) ne permet pas une telle interférence : les calculs de la fonction et des arguments ne dépendent pas des valeurs. C'est un vrai problème. Comparez

miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
  b <- mb
  if b then mt else mf

qui utilise le résultat d'un effet pour décider entre deux calculs (par exemple, le lancement de missiles et la signature d'un armistice), alors que

iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
  cond b t f = if b then t else f

qui utilise la valeur de ab de choisir entre les valeurs de deux calculs at y af qui a réalisé les deux, peut-être avec un effet tragique.

La version monadique s'appuie essentiellement sur la puissance supplémentaire de la fonction (>>=) pour choisir un calcul à partir d'une valeur, et cela peut être important. Cependant, soutenir ce pouvoir rend les monades difficiles à composer. Si nous essayons de construire des monades "à double liaison"

(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???

on est arrivé jusqu'ici, mais maintenant nos couches sont toutes mélangées. Nous avons un n (m (n t)) Nous devons donc nous débarrasser de la partie extérieure de l'image. n . Comme le dit Alexandre C, on peut le faire si l'on dispose d'une

swap :: n (m t) -> m (n t)

pour permuter les n vers l'intérieur et join à l'autre n .

Le terme plus faible "double-application" est beaucoup plus facile à définir.

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs

car il n'y a pas d'interférence entre les couches.

En conséquence, il est bon de reconnaître quand vous avez vraiment besoin de la puissance supplémentaire du Monad et lorsque l'on peut s'affranchir de la structure de calcul rigide que sont les Applicative supports.

Notez, en passant, que même si la composition de monades est difficile, elle pourrait être plus que nécessaire. Le type m (n v) indique que le calcul avec m -puis en calculant avec n -à un v -où la valeur m -Les effets se terminent avant que les n -Les effets commencent (d'où la nécessité d'un système d'alerte précoce). swap ). Si vous voulez simplement entrelacer m -effets avec n La composition est peut-être trop demandée !

3 votes

Pour l'exemple iffy, vous dites qu'il "utilise la valeur de ab pour choisir entre les valeurs de deux calculs at et af, après avoir effectué les deux, peut-être avec un effet tragique." La nature paresseuse de Haskell ne vous protège-t-elle pas contre cela ? Si j'ai list = ( \b t f -> if b then t else f) : [] et ensuite exécuter l'instruction : liste <*> pure True <*> pure "hello" <*> pure (error "bad")....J'obtiens "hello" et l'erreur ne se produit jamais. Ce code est loin d'être aussi sûr ou contrôlé qu'une monade, mais le post semble suggérer que les applicatifs provoquent une évaluation stricte. Mais dans l'ensemble, c'est un bon article ! Merci !

9 votes

Vous obtenez toujours le effets des deux, mais le pur (erreur "mauvaise") n'en a pas. Si, par contre, vous essayez iffy (pure True) (pure "hello") (error "bad"), vous obtenez une erreur que miffy évite. De plus, si vous essayez quelque chose comme iffy (pur True) (pur 0) [1,2], vous obtiendrez [0,0] au lieu de [0]. Les applicatifs ont une sorte de rigueur, en ce sens qu'ils construisent des séquences fixes de calculs, mais la fonction valeurs résultant de ces calculs sont toujours combinés paresseusement, comme vous l'observez.

0 votes

Est-il vrai que pour toute monade m y n vous pouvez toujours écrire un transformateur de monade mt et fonctionnent dans n (m t) en utilisant mt n t ? On peut donc toujours composer des monades, c'est juste plus compliqué, en utilisant des transformateurs ?

88voto

Conal Points 9874

Les applicatifs composent, les monades ne le font pas.

Monades faire composer, mais le résultat pourrait ne pas être une monade. En revanche, la composition de deux applicatifs est nécessairement un applicatif. Je soupçonne que l'intention de la déclaration originale était que "l'applicativité compose, alors que la monadité ne le fait pas". Reformulé, " Applicative est fermé par composition, et Monad n'est pas."

26 votes

De plus, deux applicatifs quelconques se composent de manière totalement mécanique, alors que la monade formée par la composition de deux monades est spécifique à cette composition.

14 votes

De plus, les monades se composent d'autres manières, le produit de deux monades est une monade, ce ne sont que les coproduits qui nécessitent une sorte de loi distributive.

1 votes

Avec, @Apocalisp, commentaire inclus, c'est la meilleure et la plus concise des réponses.

43voto

Rotsor Points 6987

Si vous avez des applicatifs A1 y A2 alors le type data A3 a = A3 (A1 (A2 a)) est également applicative (vous pouvez écrire une telle instance de manière générique).

D'autre part, si vous avez des monades M1 y M2 alors le type data M3 a = M3 (M1 (M2 a)) n'est pas nécessairement une monade (il n'y a pas d'implémentation générique sensible pour les >>= o join pour la composition).

Un exemple peut être le type [Int -> a] (ici nous composons un constructeur de type [] con (->) Int qui sont toutes deux des monades). Vous pouvez facilement écrire

app :: [Int -> (a -> b)] -> [Int -> a] -> [Int -> b]
app f x = (<*>) <$> f <*> x

Et cela se généralise à tout applicatif :

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

Mais il n'y a pas de définition sensée de

join :: [Int -> [Int -> a]] -> [Int -> a]

Si vous n'êtes pas convaincu de cela, considérez cette expression :

join [\x -> replicate x (const ())]

La longueur de la liste renvoyée doit être gravée dans le marbre avant qu'un nombre entier ne soit fourni, mais la longueur correcte de la liste dépend du nombre entier fourni. Ainsi, aucune longueur join peut exister pour ce type.

1 votes

...donc éviter les monades quand une fonction fait l'affaire ?

2 votes

@andrew, si vous vouliez dire functor, alors oui, les functors sont plus simples et devraient être utilisés lorsque cela est suffisant. Notez que ce n'est pas toujours le cas. Par exemple IO sans un Monad serait très difficile à programmer. :)

18voto

Landei Points 30509

Malheureusement, notre véritable objectif, la composition de monades, est plus difficile à atteindre. difficile. En fait, nous pouvons en fait prouver que, dans un certain sens, il n'y a aucun moyen de construire une fonction de jonction avec le type ci-dessus en utilisant seulement les opérations des deux monades (voir l'annexe pour un aperçu de la preuve). preuve). Il s'ensuit que la seule façon dont nous pouvons espérer former une composition est qu'il existe des constructions supplémentaires reliant les deux composants.

Composer des monades, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf

4 votes

Tl;dr pour les lecteurs impatients : vous pouvez composer des monades si(f ?) vous pouvez fournir une transformation naturelle swap : N M a -> M N a

0 votes

@Alexandre C. : Juste "si", je suppose. Tous les transformateurs de monades ne sont pas décrits par la composition directe de foncteurs. Par exemple, ContT r m a n'est ni m (Cont r a) ni Cont r (m a) y StateT s m a est en gros Reader s (m (Writer s a)) .

0 votes

@C. A. McCann : Je n'arrive pas à passer de (M monad, N monad, MN monad, NM monad) à (there exists swap : MN -> NM natural). Alors restons-en à "si" pour l'instant (peut-être que la réponse est dans l'article, je dois avouer que j'ai cherché rapidement)

7voto

user278559 Points 66

La solution de la loi distributive l : MN -> NM est suffisant

pour garantir la monadicité de NM. Pour voir cela, vous avez besoin d'une unité et d'une mult. Je vais me concentrer sur la mult (l'unité est unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

Cela ne no garantir que MN est une monade.

L'observation cruciale, cependant, entre en jeu lorsque vous avez des solutions de loi distributive.

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN

Ainsi, LM, LN et MN sont des monades. La question se pose de savoir si LMN est une monade (soit par

(MN)L -> L(MN) ou par N(LM) -> (LM)N

Nous avons suffisamment de structure pour réaliser ces cartes. Cependant, comme Eugenia Cheng observe nous avons besoin d'une condition hexagonale (qui revient à une présentation de l'équation de Yang-Baxter) pour garantir la monadicité de l'une ou l'autre construction. En fait, avec la condition hexagonale, les deux monades différentes coïncident.

10 votes

C'est probablement une bonne réponse, mais c'est allé whoosh bien au-dessus de ma tête.

1 votes

C'est parce que, en utilisant le terme Applicatif et la balise haskell, il s'agit d'une question sur haskell mais avec une réponse dans une notation différente.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X