Exemple concret montrant que les monades ne sont pas fermées en composition (avec preuve)?
il est bien connu que les fonctions applicatives sont fermées par composition mais pas les monades. Cependant, j'ai eu de la difficulté à trouver un contre-exemple concret montrant que les monades ne composent pas toujours.
Cette réponse donne [String -> a]
comme un exemple de non-monade. Après avoir joué un peu avec elle, je crois que c'est intuitif, mais cette réponse dit simplement "joindre ne peut pas être mis en œuvre" sans vraiment donner aucune justification. Je voudrais quelque chose de plus formel. Bien sûr, il y a beaucoup de fonctions avec le type [String -> [String -> a]] -> [String -> a]
; il faut montrer qu'une telle fonction ne répond pas nécessairement aux lois monad.
N'importe quel exemple (avec preuve d'accompagnement) fera l'affaire; Je ne suis pas nécessairement à la recherche d'une preuve de l'exemple ci-dessus en particulier.
4 réponses
considérez ce monad qui est isomorphe au (Bool ->)
monad:
data Pair a = P a a
instance Functor Pair where
fmap f (P x y) = P (f x) (f y)
instance Monad Pair where
return x = P x x
P a b >>= f = P x y
where P x _ = f a
P _ y = f b
et la composer avec le Maybe
monad:
newtype Bad a = B (Maybe (Pair a))
j'affirme que Bad
ne peut pas être une monade.
preuve partielle:
il n'y a qu'une seule façon de définir fmap
qui satisfasse fmap id = id
:
instance Functor Bad where
fmap f (B x) = B $ fmap (fmap f) x
Rappel de la monade lois:
(1) join (return x) = x
(2) join (fmap return x) = x
(3) join (join x) = join (fmap join x)
Pour la définition de return x
, nous avons deux choix: B Nothing
ou B (Just (P x x))
. Il est clair que pour avoir un quelconque espoir de retourner x
de (1) et (2), nous ne pouvons pas jeter x
, donc nous devons choisir la deuxième option.
return' :: a -> Bad a
return' x = B (Just (P x x))
qui laisse join
. Puisqu'il n'y a que quelques entrées possibles, nous pouvons faire un cas pour chacune:
join :: Bad (Bad a) -> Bad a
(A) join (B Nothing) = ???
(B) join (B (Just (P (B Nothing) (B Nothing)))) = ???
(C) join (B (Just (P (B (Just (P x1 x2))) (B Nothing)))) = ???
(D) join (B (Just (P (B Nothing) (B (Just (P x1 x2)))))) = ???
(E) join (B (Just (P (B (Just (P x1 x2))) (B (Just (P x3 x4)))))) = ???
puisque la sortie a le type Bad a
, les seules options sont B Nothing
ou B (Just (P y1 y2))
où y1
, y2
doivent être choisis de x1 ... x4
.
dans les cas (a) et (B), Nous n'avons pas de valeurs de type a
, donc nous sommes forcés de retourner B Nothing
dans les deux cas.
le cas E) est déterminé par les lois (1) et (2) monad:
-- apply (1) to (B (Just (P y1 y2)))
join (return' (B (Just (P y1 y2))))
= -- using our definition of return'
join (B (Just (P (B (Just (P y1 y2))) (B (Just (P y1 y2))))))
= -- from (1) this should equal
B (Just (P y1 y2))
pour retourner B (Just (P y1 y2))
dans le cas (E), cela signifie que nous devons choisir y1
de x1
ou x3
" ,
et y2
de x2
ou x4
.
-- apply (2) to (B (Just (P y1 y2)))
join (fmap return' (B (Just (P y1 y2))))
= -- def of fmap
join (B (Just (P (return y1) (return y2))))
= -- def of return
join (B (Just (P (B (Just (P y1 y1))) (B (Just (P y2 y2))))))
= -- from (2) this should equal
B (Just (P y1 y2))
de même, ceci dit que nous devons choisir y1
de x1
ou x2
, et y2
de x3
ou x4
. En combinant les deux,
nous déterminons que le côté droit de (E) doit être B (Just (P x1 x4))
.
jusqu'à présent tout va bien, mais le problème vient quand vous essayez de remplir les côtés de la main droite pour (C) et (d).
il y a 5 côtés droits possibles pour chacun, et aucune des combinaisons ne fonctionne. Je n'ai pas encore d'argument valable pour cela, mais j'ai un programme qui teste de manière exhaustive toutes les combinaisons:
{-# LANGUAGE ImpredicativeTypes, ScopedTypeVariables #-}
import Control.Monad (guard)
data Pair a = P a a
deriving (Eq, Show)
instance Functor Pair where
fmap f (P x y) = P (f x) (f y)
instance Monad Pair where
return x = P x x
P a b >>= f = P x y
where P x _ = f a
P _ y = f b
newtype Bad a = B (Maybe (Pair a))
deriving (Eq, Show)
instance Functor Bad where
fmap f (B x) = B $ fmap (fmap f) x
-- The only definition that could possibly work.
unit :: a -> Bad a
unit x = B (Just (P x x))
-- Number of possible definitions of join for this type. If this equals zero, no monad for you!
joins :: Integer
joins = sum $ do
-- Try all possible ways of handling cases 3 and 4 in the definition of join below.
let ways = [ \_ _ -> B Nothing
, \a b -> B (Just (P a a))
, \a b -> B (Just (P a b))
, \a b -> B (Just (P b a))
, \a b -> B (Just (P b b)) ] :: [forall a. a -> a -> Bad a]
c3 :: forall a. a -> a -> Bad a <- ways
c4 :: forall a. a -> a -> Bad a <- ways
let join :: forall a. Bad (Bad a) -> Bad a
join (B Nothing) = B Nothing -- no choice
join (B (Just (P (B Nothing) (B Nothing)))) = B Nothing -- again, no choice
join (B (Just (P (B (Just (P x1 x2))) (B Nothing)))) = c3 x1 x2
join (B (Just (P (B Nothing) (B (Just (P x3 x4)))))) = c4 x3 x4
join (B (Just (P (B (Just (P x1 x2))) (B (Just (P x3 x4)))))) = B (Just (P x1 x4)) -- derived from monad laws
-- We've already learnt all we can from these two, but I decided to leave them in anyway.
guard $ all (\x -> join (unit x) == x) bad1
guard $ all (\x -> join (fmap unit x) == x) bad1
-- This is the one that matters
guard $ all (\x -> join (join x) == join (fmap join x)) bad3
return 1
main = putStrLn $ show joins ++ " combinations work."
-- Functions for making all the different forms of Bad values containing distinct Ints.
bad1 :: [Bad Int]
bad1 = map fst (bad1' 1)
bad3 :: [Bad (Bad (Bad Int))]
bad3 = map fst (bad3' 1)
bad1' :: Int -> [(Bad Int, Int)]
bad1' n = [(B Nothing, n), (B (Just (P n (n+1))), n+2)]
bad2' :: Int -> [(Bad (Bad Int), Int)]
bad2' n = (B Nothing, n) : do
(x, n') <- bad1' n
(y, n'') <- bad1' n'
return (B (Just (P x y)), n'')
bad3' :: Int -> [(Bad (Bad (Bad Int)), Int)]
bad3' n = (B Nothing, n) : do
(x, n') <- bad2' n
(y, n'') <- bad2' n'
return (B (Just (P x y)), n'')
pour un petit contre-exemple de béton, considérez le terminal monad.
data Thud x = Thud
Le return
et >>=
just go Thud
, et les lois tenir trivialement.
nous allons maintenant avoir aussi l'écrivain monad pour Bool (avec, disons, la structure XOR-monoid).
data Flip x = Flip Bool x
instance Monad Flip where
return x = Flip False x
Flip False x >>= f = f x
Flip True x >>= f = Flip (not b) y where Flip b y = f x
er, um, nous aurons besoin de composition
newtype (:.:) f g x = C (f (g x))
essayez maintenant de définir...
instance Monad (Flip :.: Thud) where -- that's effectively the constant `Bool` functor
return x = C (Flip ??? Thud)
...
Parametricity nous dit que ???
ne peut pas dépendre en aucune façon utile de x
, il doit donc être une constante. En conséquence, join . return
est nécessairement une fonction constante aussi, d'où la loi
join . return = id
doit échouer pour n'importe quelles définitions de join
et return
nous choisissons.
la Construction du tiers exclu
(->) r
est une monade pour chaque r
et Either e
est une monade pour chaque e
. Définissons leur composition ( (->) r
intérieur, Either e
extérieur):
import Control.Monad
newtype Comp r e a = Comp { uncomp :: Either e (r -> a) }
je prétends que si Comp r e
était un monade pour chaque r
et e
alors nous pourrions réaliser la loi du milieu exclu 1519570920" . Cela n'est pas possible dans logique intuitionniste qui sous-tend les systèmes de caractères des langues fonctionnelles (avoir la loi du milieu exclu équivaut à avoir l'opérateur call/cc ).
supposons que Comp
soit une monade. Puis nous avons
join :: Comp r e (Comp r e a) -> Comp r e a
et donc nous pouvons définir
swap :: (r -> Either e a) -> Either e (r -> a)
swap = uncomp . join . Comp . return . liftM (Comp . liftM return)
(c'est juste la fonction swap
de papier Composer des monades que Brent mentionne, sect. 4.3, seulement avec les constructeurs newtype (de)ajoutés. Notez que nous ne nous soucions pas des propriétés qu'il possède, la seule chose importante est qu'il est définissable et total.)
maintenant, mettons
data False -- an empty datatype corresponding to logical false
type Neg a = (a -> False) -- corresponds to logical negation
et spécialisé swap pour r = b
, e = b
, a = False
:
excludedMiddle :: Either b (Neg b)
excludedMiddle = swap Left
Conclusion: même si (->) r
et Either r
sont des monades, leur composition Comp r r
ne peut pas l'être.
Note: cela se reflète également dans la façon dont ReaderT
et EitherT
sont définis. les deux ReaderT r (Either e)
et EitherT e (Reader r)
sont isomorphiques à r -> Either e a
! Il n'y a aucune façon de définir monad pour le dual Either e (r -> a)
.
échapper IO
actions
il y a beaucoup d'exemples dans la même veine qui impliquent IO
et qui conduisent à échapper IO
d'une façon ou d'une autre. Par exemple:
newtype Comp r a = Comp { uncomp :: IO (r -> a) }
swap :: (r -> IO a) -> IO (r -> a)
swap = uncomp . join . Comp . return . liftM (Comp . liftM return)
Maintenant, nous allons avoir
main :: IO ()
main = do
let foo True = print "First" >> return 1
foo False = print "Second" >> return 2
f <- swap foo
input <- readLn
print (f input)
que se passera-t-il lorsque ce programme sera exécuté? Il y a deux possibilités:
- "Premier" ou "Deuxième" est imprimé après nous avons lu
input
à partir de la console, ce qui signifie que la séquence des actions a été inversé et que les actions defoo
échappé à purf
. -
ou
swap
(d'oùjoin
) jette l'actionIO
et ni "premier" ni "deuxième" n'est jamais imprimé. Mais cela signifie quejoin
viole la loijoin . return = id
parce que si
join
jette leIO
action loin, puisfoo ≠ (join . return) foo
autres combinaisons similaires IO
+ monades conduisent à la construction
swapEither :: IO (Either e a) -> Either e (IO a)
swapWriter :: (Monoid e) => IO (Writer e a) -> Writer e (IO a)
swapState :: IO (State e a) -> State e (IO a)
...
soit leurs implémentations join
doivent permettre à e
de s'échapper de IO
soit ils doivent le jeter et le remplacer par quelque chose d'autre, en violation de la loi.
votre lien renvoie à ce type de données, alors essayons de choisir une implémentation spécifique: data A3 a = A3 (A1 (A2 a))
je choisirai arbitrairement A1 = IO, A2 = []
. Nous allons aussi en faire un newtype
et lui donner un nom particulièrement pointu, pour le plaisir:
newtype ListT IO a = ListT (IO [a])
trouvons une action arbitraire dans ce type, et exécutons-la de deux façons différentes mais égales:
λ> let v n = ListT $ do {putStr (show n); return [0, 1]}
λ> runListT $ ((v >=> v) >=> v) 0
0010101[0,1,0,1,0,1,0,1]
λ> runListT $ (v >=> (v >=> v)) 0
0001101[0,1,0,1,0,1,0,1]
Comme vous pouvez le voir, cela enfreint la loi de l'associativité: ∀x y z. (x >=> y) >=> z == x >=> (y >=> z)
.
Il s'avère, ListT m
n'est qu'une monade si m
est un commutative monade. Cela empêche une grande catégorie de monades de composer avec []
, ce qui enfreint la règle universelle de "composer deux monades arbitraires donne une monade".
voir aussi: https://stackoverflow.com/a/12617918/1769569