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.

75
demandé sur Community 2012-10-23 19:41:20

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))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'')
40
répondu hammar 2012-10-26 10:21:09

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.

35
répondu pigworker 2012-11-03 12:43:18

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:

  1. "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 de foo échappé à pur f .
  2. ou swap (d'où join ) jette l'action IO et ni "premier" ni "deuxième" n'est jamais imprimé. Mais cela signifie que join viole la loi

    join . return = id
    

    parce que si join jette le IO action loin, puis

    foo ≠ (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.

33
répondu Petr Pudlák 2013-12-04 21:04:07

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

4
répondu hpc 2017-05-23 12:17:44