Es bien sabido que los functores aplicativos están cerrados bajo composición, pero las mónadas no. Sin embargo, he tenido problemas para encontrar un contraejemplo concreto que muestre que las mónadas no siempre componen.
Esta respuesta da [String -> a]
como ejemplo de una no mónada. Después de jugar un poco con él, lo creo intuitivamente, pero esa respuesta simplemente dice "la unión no se puede implementar" sin realmente dar ninguna justificación. Quisiera algo más formal. Por supuesto, hay muchas funciones con tipo [String -> [String -> a]] -> [String -> a]
; hay que demostrar que tal función no satisface necesariamente las leyes de la mónada.
Cualquier ejemplo (con prueba adjunta) servirá; No estoy buscando necesariamente una prueba del ejemplo anterior en particular.
fuente
join
para la composición de dos mónadas en general . Pero esto no conduce a ningún ejemplo concreto .Respuestas:
Considere esta mónada que es isomorfa a la
(Bool ->)
mónada: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
y compónelo con la
Maybe
mónada:newtype Bad a = B (Maybe (Pair a))
Afirmo que
Bad
no puede ser una mónada.Prueba parcial:
Solo hay una forma de definir
fmap
que satisfacefmap id = id
:instance Functor Bad where fmap f (B x) = B $ fmap (fmap f) x
Recuerde las leyes de las mónadas:
(1) join (return x) = x (2) join (fmap return x) = x (3) join (join x) = join (fmap join x)
Para la definición de
return x
, tenemos dos opciones:B Nothing
oB (Just (P x x))
. Está claro que para tener alguna esperanza de regresarx
de (1) y (2), no podemos tirarx
, por lo que tenemos que elegir la segunda opción.return' :: a -> Bad a return' x = B (Just (P x x))
Eso se va
join
. Dado que solo hay unas pocas entradas posibles, podemos hacer un caso para cada una: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)))))) = ???
Dado que la salida tiene tipo
Bad a
, las únicas opciones sonB Nothing
oB (Just (P y1 y2))
dóndey1
,y2
deben elegirsex1 ... x4
.En los casos (A) y (B), no tenemos valores de tipo
a
, por lo que estamos obligados a regresarB Nothing
en ambos casos.El caso (E) está determinado por las leyes de las mónadas (1) y (2):
-- 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))
Para devolver
B (Just (P y1 y2))
en el caso (E), esto significa que debemos elegiry1
entrex1
ox3
, yy2
entrex2
ox4
.-- 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))
Asimismo, esto dice que debemos elegir
y1
entrex1
ox2
, yy2
entrex3
ox4
. Combinando los dos, determinamos que el lado derecho de (E) debe serB (Just (P x1 x4))
.Hasta ahora todo está bien, pero el problema surge cuando intentas completar los lados derechos para (C) y (D).
Hay 5 lados derechos posibles para cada uno y ninguna de las combinaciones funciona. Todavía no tengo un buen argumento para esto, pero tengo un programa que prueba exhaustivamente todas las combinaciones:
{-# 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'')
fuente
swap :: Pair (Maybe a) -> Maybe (Pair a)
.(Maybe a, Maybe a)
es una mónada (porque es un producto de dos mónadas) peroMaybe (a, a)
no es una mónada. También he comprobado queMaybe (a,a)
no es una mónada mediante cálculos explícitos.Maybe (a, a)
no es una mónada? Tanto Maybe como Tuple son transitables y deberían poder componerse en cualquier orden; Hay otras preguntas de SO que también hablan de este ejemplo específico.Para un pequeño contraejemplo concreto, considere la mónada terminal.
data Thud x = Thud
El
return
y>>=
simplemente veteThud
, y las leyes se cumplen trivialmente.Ahora tengamos también la mónada de escritura para Bool (con, digamos, la estructura xor-monoide).
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, necesitaremos composición
newtype (:.:) f g x = C (f (g x))
Ahora intenta definir ...
instance Monad (Flip :.: Thud) where -- that's effectively the constant `Bool` functor return x = C (Flip ??? Thud) ...
La parametricidad nos dice que
???
no puede depender de ninguna manera útilx
, por lo que debe ser una constante. Como resultado,join . return
es necesariamente una función constante también, de ahí la leyjoin . return = id
Debe fallar para cualquier definición de
join
yreturn
que elijamos.fuente
Construyendo el medio excluido
(->) r
es una mónada para todosr
yEither e
es una mónada para todose
. Definamos su composición ((->) r
interior,Either e
exterior):import Control.Monad newtype Comp r e a = Comp { uncomp :: Either e (r -> a) }
Afirmo que si
Comp r e
fuera una mónada para todosr
ye
entonces podríamos realizar la ley del medio excluido . Esto no es posible en la lógica intuicionista que subyace a los tipos de sistemas de lenguajes funcionales (tener la ley del medio excluido es equivalente a tener el operador call / cc ).Supongamos que
Comp
es una mónada. Entonces tenemosjoin :: Comp r e (Comp r e a) -> Comp r e a
y así podemos definir
swap :: (r -> Either e a) -> Either e (r -> a) swap = uncomp . join . Comp . return . liftM (Comp . liftM return)
(Esta es solo la
swap
función del papel Componer mónadas que menciona Brent, Sección 4.3, solo con los constructores (de) de newtype agregados. Tenga en cuenta que no nos importa qué propiedades tiene, lo único importante es que es definible y total .)Ahora vamos a configurar
data False -- an empty datatype corresponding to logical false type Neg a = (a -> False) -- corresponds to logical negation
y especializado de intercambio para
r = b
,e = b
,a = False
:excludedMiddle :: Either b (Neg b) excludedMiddle = swap Left
Conclusión: Aunque
(->) r
yEither r
son mónadas, su composiciónComp r r
no puede serlo.Nota: Que esto también se refleja en la forma
ReaderT
yEitherT
se definen. ¡AmbosReaderT r (Either e)
yEitherT e (Reader r)
son isomorfos ar -> Either e a
! No hay forma de definir la mónada para el dualEither e (r -> a)
.IO
Acciones de escapeHay muchos ejemplos en la misma línea que involucran
IO
y que conducen a escapar deIO
alguna manera. Por ejemplo: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)
Ahora tengamos
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)
¿Qué pasará cuando se ejecute este programa? Hay dos posibilidades:
input
desde la consola, lo que significa que la secuencia de acciones se invirtió y que las acciones defoo
escapó a purof
.O
swap
(por lo tantojoin
) descarta laIO
acción y nunca se imprime ni "Primero" ni "Segundo". Pero esto significa quejoin
viola la leyjoin . return = id
porque si
join
desecha laIO
acción, entoncesfoo ≠ (join . return) foo
Otras
IO
combinaciones similares de mónadas + conducen a la construcciónswapEither :: 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) ...
O sus
join
implementaciones deben permitire
escaparIO
o deben tirarlo y reemplazarlo con otra cosa, violando la ley.fuente
<*>
en su lugar), intenté editarlo pero me dijeron que mi edición era demasiado corta.) --- No está claro yo que tener una definición dejoin
implica una definición deswap
. ¿Podrías ampliarlo? El artículo referido por Brent parece implicar que para pasar dejoin
aswap
necesitamos los siguientes supuestos:joinM . fmapM join = join . joinM
yjoin . fmap (fmapM joinN ) = fmapM joinN . join
donde joinM = join :: M, etc.swap
que coincida con el papel). No revisé el papel hasta ahora, y tienes razón, parece que necesitamos J (1) y J (2) para definirswap
<->join
. Este es quizás un punto débil de mi prueba y lo pensaré más (tal vez sería posible obtener algo del hecho de que lo esApplicative
).join
, podríamos definirswap :: (Int -> Maybe a) -> Maybe (Int -> a)
usando la definición anterior (sin importar las leyes que estoswap
satisfaga). ¿Cómo seswap
comportaría tal ? Al tener noInt
, no tiene nada que pasar a su argumento, por lo que tendría que regresarNothing
para todas las entradas. Creo que podemos contradecirjoin
las leyes de las mónadas sin necesidad de definirjoin
desdeswap
atrás. Lo comprobaré y te lo haré saber.Su enlace hace referencia a este tipo de datos, así que intentemos elegir una implementación específica:
data A3 a = A3 (A1 (A2 a))
Elegiré arbitrariamente
A1 = IO, A2 = []
. También lo haremosnewtype
y le daremos un nombre particularmente puntiagudo, por diversión:newtype ListT IO a = ListT (IO [a])
Vamos a pensar en una acción arbitraria de ese tipo y ejecutarla de dos formas diferentes pero iguales:
λ> 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]
Como se puede ver, esto rompe la ley asociatividad:
∀x y z. (x >=> y) >=> z == x >=> (y >=> z)
.Resulta que
ListT m
es solo una mónada sim
es una mónada conmutativa . Esto evita que se componga una gran categoría de mónadas[]
, lo que rompe la regla universal de "componer dos mónadas arbitrarias produce una mónada".Véase también: https://stackoverflow.com/a/12617918/1769569
fuente
ListT
no produce una mónada en todos los casos, en lugar de mostrar que ninguna definición posible puede funcionar.Monad
instanciaListT
y una demostración de que no hay otras. La declaración es "para todo esto, existe eso" y, por lo tanto, la negación es "existe esto tal que para todo aquello"Las mónadas no componen. No de forma genérica, no existe una forma general de componer mónadas. Ver https://www.slideshare.net/pjschwarz/monads-do-not-compose
fuente