Monadların kompozisyon altında kapatılmadığını gösteren somut örnek (kanıtla)


82

Kompozisyon altında uygulama işlevlerinin kapalı olduğu ancak monadların kapalı olduğu iyi bilinmektedir. Bununla birlikte, monadların her zaman beste yapmadığını gösteren somut bir karşı örnek bulmakta zorlanıyorum.

Bu cevap , [String -> a]monad olmayan bir örnek olarak verir . Bir süre onunla oynadıktan sonra, sezgisel olarak inanıyorum, ancak bu cevap, gerçekten herhangi bir gerekçe göstermeden sadece "katılma uygulanamaz" diyor. Daha resmi bir şey istiyorum. Elbette türle ilgili birçok işlev vardır [String -> [String -> a]] -> [String -> a]; Böyle bir işlevin zorunlu olarak monad yasalarını karşılamadığı gösterilmelidir.

Herhangi bir örnek (ispatla birlikte) işe yarar; Özellikle yukarıdaki örneğin bir kanıtını aramıyorum.


Bulabildiğim en yakın şey, web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf ekidir ; bu, birçok basitleştirici varsayım altında join, iki monadın bileşimi için yazmanın imkansız olduğunu gösterir . genel . Ancak bu herhangi bir somut örneğe götürmez .
Brent Yorgey

Yeni Computer Science Stack Exchange sitesi olan cs.stackexchange.com'da bu soruya daha iyi yanıtlar alabilirsiniz.
Patrick87

3
Belki anlamıyorum ama bence soru daha kesin bir şekilde tanımlanabilir. İki monad "bestelemek" derken, basitçe tip kurucuları oluşturmaktan mı bahsediyorsunuz? Ve sonuç "monad değil" olduğunda, bu, bu tür bir construcor'un monad örneğinin yazılamayacağı anlamına mı gelir? Ve, birleşik tip kurucu için bir monad örneği yazılabiliyorsa, iki faktörlü monadın örnekleriyle herhangi bir ilişkisi olması gerekir mi, yoksa tamamen alakasız olabilir mi?
Owen

1
Evet, tip kurucuları oluşturmayı kastediyorum; "monad değil" geçerli (yasal) bir monad örneğinin yazılamayacağı anlamına gelir; ve kompozisyon örneğinin faktörlerin örnekleriyle herhangi bir ilişkisi olup olmadığı umurumda değil.
Brent Yorgey

Yanıtlar:


42

Monad için izomorfik olan bu monadı düşünün (Bool ->):

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

ve Maybemonad ile besteleyin :

newtype Bad a = B (Maybe (Pair a))

Bunun Badbir monad olamayacağını iddia ediyorum .


Kısmi kanıt:

fmapTatmin edici olanı tanımlamanın tek bir yolu var fmap id = id:

instance Functor Bad where
    fmap f (B x) = B $ fmap (fmap f) x

Monad yasalarını hatırlayın:

(1) join (return x) = x 
(2) join (fmap return x) = x
(3) join (join x) = join (fmap join x)

Tanımı return xiçin iki seçeneğimiz var: B Nothingveya B (Just (P x x)). x(1) ve (2) 'den geri dönme ümidine sahip olmak için bir kenara atamayacağımız açıktır x, bu yüzden ikinci seçeneği seçmeliyiz.

return' :: a -> Bad a
return' x = B (Just (P x x))

O bırakır join. Yalnızca birkaç olası girdi olduğundan, her biri için bir durum oluşturabiliriz:

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)))))) = ???

Çıktı türünü sahip olduğundan Bad a, sadece seçenekleridir B Nothingveya B (Just (P y1 y2))nerede y1, y2seçilen lazım x1 ... x4.

(A) ve (B) durumlarında tür değerimiz yoktur a, bu nedenle B Nothingher iki durumda da geri dönmek zorunda kalırız .

Durum (E), (1) ve (2) monad yasaları ile belirlenir:

-- 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))

Dönmek için B (Just (P y1 y2))(e) 'de, bu araçlar, biz almak gerekir y1birinden x1ya da x3, ve y2gelen ya x2ya da 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))

Aynı şekilde, bu biz almak gerektiğini söyledi y1birinden x1veya x2ve y2gelen ya x3ya x4. İkisini birleştirerek, (E) 'nin sağ tarafının olması gerektiğini belirledik B (Just (P x1 x4)).

Şimdiye kadar her şey yolunda, ancak sorun (C) ve (D) için sağ tarafları doldurmaya çalıştığınızda ortaya çıkıyor.

Her biri için 5 olası sağ taraf vardır ve kombinasyonların hiçbiri çalışmaz. Henüz bunun için güzel bir argümanım yok, ancak tüm kombinasyonları kapsamlı bir şekilde test eden bir programım var:

{-# 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'')

Teşekkürler, ikna oldum Kanıtınızı basitleştirmenin yolları olup olmadığını merak etmeme neden oluyor.
Brent Yorgey

1
@BrentYorgey: Olması gerektiğinden şüpheleniyorum, çünkü (C) ve (D) vakalarıyla ilgili sorunlar, tanımlamaya çalışırken yaşadığınız sorunlara çok benziyor swap :: Pair (Maybe a) -> Maybe (Pair a).
hammar

11
Kısacası, monadların bilgiyi atmasına izin verilir ve eğer kendi içlerinde iç içe geçmişlerse sorun değil. Ancak, bilgiyi koruyan bir monad ve bilgi bırakan bir monadınız olduğunda, iki damla bilgisini birleştiren, bilgiyi koruyan kişinin kendi monad yasalarını karşılamak için bu bilginin tutulmasına ihtiyaç duymasına rağmen. Yani rastgele monadları birleştiremezsiniz. (Bu nedenle, ilgili bilgileri bırakmayacaklarını garanti eden Traversable monad'lara ihtiyacınız var; bunlar keyfi olarak oluşturulabilir.) Sezgi için teşekkürler!
Xanthir

@Xanthir Composing yalnızca bir sırayla çalışır: (Maybe a, Maybe a)bir monaddır (çünkü iki monadın bir ürünüdür) ancak Maybe (a, a)bir monad değildir. Maybe (a,a)Açık hesaplamalarla bunun bir monad olmadığını da doğruladım .
winitzki

Neden Maybe (a, a)bir monad olmadığını gösterir misin? Hem Belki hem de Tuple Çaprazlanabilir ve herhangi bir sırada oluşturulabilir olmalıdır; bu özel örnek hakkında konuşan başka SO soruları da var.
Xanthir

38

Küçük bir somut karşı örnek için, terminal monadını düşünün.

data Thud x = Thud

returnVe >>=sadece gitmek Thudve yasalar trivially tutun.

Şimdi Bool için yazar monadına da sahip olalım (diyelim ki xor-monoid yapı ile).

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, kompozisyona ihtiyacımız olacak

newtype (:.:) f g x = C (f (g x))

Şimdi tanımlamaya çalışın ...

instance Monad (Flip :.: Thud) where  -- that's effectively the constant `Bool` functor
  return x = C (Flip ??? Thud)
  ...

Parametriklik, bize ???herhangi bir yararlı şekilde bağlı olamayacağını söyler x, bu nedenle sabit olması gerekir. Sonuç olarak, join . returnzorunlu olarak sabit bir işlevdir, dolayısıyla yasa

join . return = id

Tanımı ne olursa olsun başarısız olmalı joinve returnbiz seçiyoruz.


3
Carlo'nun Hamalainen blogunda, yararlı bulduğum yukarıdaki yanıtın ek, çok açık ve ayrıntılı bir analizi var: carlo-hamalainen.net/blog/2014/1/2/…
paluh

34

Dışlanmış orta inşa etmek

(->) rHer bir monad olan rve Either eher bir monad olan e. Kompozisyonlarını tanımlayalım ( (->) riçeride, Either edışarıda):

import Control.Monad
newtype Comp r e a = Comp { uncomp :: Either e (r -> a) }

Ben iddia eğer Comp r eher bir monad vardı rve eo zaman elde edebileceği Dışlandı ortada kanununu . Bu, işlevsel dillerin tür sistemlerinin temelini oluşturan sezgisel mantıkta mümkün değildir (dışlanmış orta yasasına sahip olmak, çağrı / cc operatörüne sahip olmakla eşdeğerdir ).

CompBir monad olduğunu varsayalım . O zaman sahibiz

join :: Comp r e (Comp r e a) -> Comp r e a

ve böylece tanımlayabiliriz

swap :: (r -> Either e a) -> Either e (r -> a)
swap = uncomp . join . Comp . return . liftM (Comp . liftM return)

(Bu sadece bir swapkağıt gelen fonksiyon oluşturma monads , Tarikatı Brent bahseder. 4.3, yalnızca newtype en (de) kurucular eklenen. Biz neler özellikleri umurumda değil Not olduğunu, ancak önemli olan şey, tanımlanabilen ve toplam olmasıdır .)

Şimdi ayarlayalım

data False -- an empty datatype corresponding to logical false
type Neg a = (a -> False) -- corresponds to logical negation

ve takas için uzmanlaşmak r = b, e = b, a = False:

excludedMiddle :: Either b (Neg b)
excludedMiddle = swap Left

Sonuç: olsa (->) rve Either rmonads vardır, onların kompozisyon Comp r rolamaz.

Not: Bunun nasıl ReaderTve EitherTtanımlandığına da yansıdığını . Hem ReaderT r (Either e) ve EitherT e (Reader r)izomorf olan r -> Either e a! İkili için monad'ı nasıl tanımlamanın bir yolu yoktur Either e (r -> a).


Kaçan IOeylemler

Aynı damarda yer alan IOve bir IOşekilde kaçmaya neden olan birçok örnek var . Örneğin:

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)

Şimdi sahip olalım

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)

Bu program çalıştırıldığında ne olacak? İki olasılık vardır:

  1. Konsoldan okuduktan sonra "Birinci" veya "İkinci" yazdırılır input, bu, eylemlerin sırasının tersine çevrildiği ve eylemlerin foosaftan kaçtığı anlamına gelir f.
  2. Veya swap(dolayısıyla join) IOeylemi atar ve ne "Birinci" ne de "İkinci" hiçbir zaman yazdırılmaz. Ama bu join, kanunu ihlal ettiği anlamına gelir

    join . return = id
    

    çünkü eylemi bir joinkenara atarsa IO, o zaman

    foo ≠ (join . return) foo
    

Diğer benzer IO+ monad kombinasyonları,

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)
...

Ya onların joinuygulamaları izin vermelidir ekaçmak için IOya da yasaları ihlal, at onu ve başka bir şey ile değiştirmelisiniz.


(Sanırım "ap", "fmap, pure ve ap'nin kanonik tanımlar olduğu" bir yazım hatasıdır ( <*>bunun yerine olmalı ), düzenlemeye çalıştım ama bana düzenlememin çok kısa olduğu söylendi.) --- için bir tanıma sahip olmanın bir tanımını joinima ettiğini düşünüyorum swap. Bunu genişletebilir misin? Brent tarafından yönlendirilen kağıt gitmek için ima etmektedir joiniçin swapaşağıdaki varsayımları gerekir: joinM . fmapM join = join . joinMve join . fmap (fmapM joinN ) = fmapM joinN . join nerede joinM = katılmak :: vb M,
Rafael Caetano

1
@RafaelCaetano Yazım hatası için teşekkürler, onu düzelttim (ve ayrıca işlevi swapkağıda uyacak şekilde yeniden adlandırdım ). Şu ana kadar kağıdı kontrol etmedim ve haklısınız, görünüşe göre swap<-> ' yi tanımlamak için J (1) ve J (2)' ye ihtiyacımız var join. Bu belki de ispatımın zayıf bir noktası ve bunun hakkında daha fazla düşüneceğim (belki de olduğu gerçeğinden bir şeyler elde etmek mümkün olabilir Applicative).
Petr

@RafaelCaetano Ama ispatın hala geçerli olduğuna inanıyorum: Yapmış olsaydık , yukarıdaki tanımı kullanarak jointanımlayabilirdik swap :: (Int -> Maybe a) -> Maybe (Int -> a)(bu hangi kanunları swapkarşıladığına bakılmaksızın ). Nasıl swapdavranırdı? Hayır olmadığından Int, argümanına aktaracak hiçbir şeyi yoktur , bu nedenle Nothingtüm girdiler için geri dönmesi gerekir . Ne için bir çelişki elde edebilirsiniz inanıyoruz jointanımlamak gerek kalmadan bireyin monad yasaları joingelen swaparka. Kontrol edip size haber vereceğim.
Petr

@Petr: Halen bu aradığım kanıtın tam olarak bu olmadığı konusunda Rafael'e katılıyorum, ancak bahsettiğiniz hatlarda düzeltilip düzeltilemeyeceğini de merak ediyorum.
Brent Yorgey

1
@ PetrPudlák Vay canına, çok güzel! Evet, şimdi tamamen alıyorum. Bunlar gerçekten ilginç bilgiler. Monad kanunlarından herhangi birine atıfta bulunulmadan, basitçe takas kurabilmenin bir çelişkiye yol açacağını tahmin edemezdim! Birden fazla yanıtı kabul edebilseydim, bunu da kabul ederdim.
Brent Yorgey

4

Bağlantınız bu veri türüne referans veriyor, bu yüzden bazı özel uygulamaları seçmeyi deneyelim: data A3 a = A3 (A1 (A2 a))

Ben keyfi olarak seçeceğim A1 = IO, A2 = []. Ayrıca newtypeeğlenmek için onu bir hale getirip özellikle sivri uçlu bir isim vereceğiz:

newtype ListT IO a = ListT (IO [a])

Bu türde bazı keyfi eylemler bulalım ve bunu iki farklı ama eşit şekilde çalıştıralım:

λ> 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]

Gördüğünüz gibi, bu çağrışımsallık yasaları çiğnemesini: ∀x y z. (x >=> y) >=> z == x >=> (y >=> z).

O, çıkıyor ListT meğer sadece monad olan mbir olduğunu değişmeli monad. Bu, geniş []bir monad kategorisinin, "iki rastgele monad oluşturmak bir monad verir" evrensel kuralını kıran, beste yapmasını engeller .

Ayrıca bkz .: https://stackoverflow.com/a/12617918/1769569


11
Bunun, olası hiçbir tanımın işe yaramayacağını ListTgöstermek yerine, yalnızca belirli bir monad tanımının her durumda bir monad üretmede başarısız olduğunu gösterdiğini düşünüyorum .
CA McCann

Mecbur değilim. "Tüm bunlara rağmen" nin olumsuzlaması, "bir karşı örnek vardır" dır. Sorulan soru, "tüm monadlar için, kompozisyonları bir monad oluşturur" idi. Kendi başına monad olan ancak beste yapamayan türlerin bir kombinasyonunu gösterdim.
hpc

11
@hpc, ancak iki monadın bileşimi, türlerinin bileşiminden daha fazladır. Ayrıca operasyonlara da ihtiyacınız var ve benim Brent'in sorusuyla ilgili yorumum şu: operasyonların uygulanmasını elde etmenin metodik bir yolu olmayabilir - daha güçlü bir şey arıyor, bazı kompozisyonların kanunları karşılayan hiçbir operasyonu yok. mekanik olarak türetilebilir veya değil. bu mantıklı mı?
luqui

Evet, luqui haklı. Orijinal sorum net değilse özür dilerim.
Brent Yorgey

Bu cevapta gerçekten eksik olan şey, Monadörnek ListTve başkalarının olmadığını gösteren bir gösteridir. İfade "tüm bunlara rağmen, şu var" ve bu nedenle olumsuzlama, "her şey için böyle bir şey var"
Ben Millwood

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.