Les candidats composent, les monades non.
Que signifie la déclaration ci-dessus? Et quand est-on préférable à l'autre?
Si nous comparons les types
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
nous obtenons un indice sur 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. L'opérateur (<*>)
N'autorise pas de telles interférences: les calculs de fonction et d'argument ne dépendent pas des valeurs. Cela mord vraiment. Comparer
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 certain effet pour choisir entre deux calculs (par exemple, lancement de missiles et signature d'un armistice), tandis 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
pour choisir entre les valeurs de deux calculs at
et af
, après avoir effectué les deux, peut-être avec un effet tragique.
La version monadique repose essentiellement sur la puissance supplémentaire de (>>=)
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 créer une "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 ???
nous arrivons jusqu'ici, mais maintenant nos couches sont toutes mélangées. Nous avons une n (m (n t))
, nous devons donc nous débarrasser de n
externe. Comme le dit Alexandre C, nous pouvons le faire si nous avons un
swap :: n (m t) -> m (n t)
pour permuter les n
vers l'intérieur et join
vers les autres n
.
La "double application" la plus faible 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.
De même, il est bon de reconnaître quand vous avez vraiment besoin de la puissance supplémentaire de Monad
et quand vous pouvez vous en sortir avec la structure de calcul rigide prise en charge par Applicative
.
Notez, en passant, que bien que la composition de monades soit difficile, elle pourrait être plus que nécessaire. Le type m (n v)
indique le calcul avec m
- effets, puis le calcul avec n
- effets avec une valeur v
-, où m
les effets se terminent avant le début des effets n
(d'où la nécessité de swap
). Si vous voulez juste entrelacer m
- effets avec n
- effets, alors la composition est peut-être trop demander!
Les candidats composent, les monades non.
Monades do composez, mais le résultat peut ne pas être une monade. En revanche, la composition de deux applicatifs est nécessairement applicative. Je soupçonne que l'intention de la déclaration originale était que "l'applicativité compose, tandis que la monadité ne fait pas. Reformulé, "Applicative
est fermé sous composition et Monad
ne l'est pas."
Si vous avez des applicatifs A1
Et A2
, Alors le type data A3 a = A3 (A1 (A2 a))
est également applicatif (vous pouvez écrire une telle instance de manière générique).
En revanche, si vous avez des monades M1
Et M2
, 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 sensée pour >>=
ou join
pour la composition).
Un exemple peut être le type [Int -> a]
(Ici nous composons un constructeur de type []
Avec (->) Int
, Qui sont tous les 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 retournée doit être définie dans la pierre avant qu'un entier ne soit jamais fourni, mais sa longueur correcte dépend de l'entier qui est fourni. Ainsi, aucune fonction join
correcte ne peut exister pour ce type.
Malheureusement, notre véritable objectif, la composition des monades, est plutôt plus difficile. .. En fait, nous pouvons réellement prouver que, dans un certain sens, il n'y a aucun moyen de construire une fonction de jointure avec le type ci-dessus en utilisant uniquement les opérations des deux monades (voir l'annexe pour un aperçu de la preuve). Il s'ensuit que la seule façon d'espérer former une composition est s'il existe des constructions supplémentaires reliant les deux composants.
Composer des monades, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf
La solution de loi distributive l: MN -> NM suffit
pour garantir la monadicité de NM. Pour voir cela, vous avez besoin d'une unité et d'un mult. je vais me concentrer sur le mult (l'unité est unit_N unitM)
NMNM - l -> NNMM - mult_N mult_M -> NM
Cela ne garantit pas que MN est une monade.
Cependant, l'observation cruciale entre en jeu lorsque vous avez des solutions de droit distributif
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 assez de structure pour faire ces cartes. Cependant, comme Eugenia Cheng observe , nous avons besoin d'une condition hexagonale (ce 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.