モナドが合成の下で閉じられていないことを示す具体的な例(証拠付き)?


82

適用可能なファンクターは構成の下で閉じられますが、モナドは閉じられないことはよく知られています。しかし、モナドが常に構成されているとは限らないことを示す具体的な反例を見つけるのに苦労しています。

この答え[String -> a]、非モナドの例として示しています。少し遊んだ後は直感的に信じますが、その答えは「結合は実装できない」と言っているだけで、正当な理由はありません。もっとフォーマルなものが欲しいのですが。もちろん、タイプの関数はたくさんあります[String -> [String -> a]] -> [String -> a]。そのような関数は必ずしもモナドの法則を満たさないことを示さなければなりません。

(証拠を伴う)どんな例でもかまいません。特に上記の例の証拠を必ずしも探しているわけではありません。


私は見つけることができる最も近いの付録であるweb.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf仮定を簡素化し、多くの下、それを書くことは不可能であることを示し、join2つのモナドの組成物で一般。しかし、これは具体的な例にはつながりません。
Brent Yorgey 2012年

この質問に対するより良い回答は、新しいComputer Science StackExchangeサイトであるcs.stackexchange.comで入手できます。
Patrick87 2012年

3
理解できないかもしれませんが、質問はもっと正確に定義できると思います。2つのモナドを「構成する」と言うとき、それは単に型コンストラクターを構成することを意味しますか?そして、結果が「モナドではない」場合、これは、そのタイプの構造のモナドインスタンスを記述できないことを意味しますか?また、合成型コンストラクターのモナドインスタンスを記述できる場合、2つの要素のモナドのインスタンスと何らかの関係がある必要がありますか、それともまったく関係がないのでしょうか。
オーウェン

1
はい、私は型コンストラクターを作成することを意味します。「モナドではない」とは、有効な(合法的な)モナドインスタンスを記述できないことを意味します。そして、コンポジションのインスタンスがファクターのインスタンスと何らかの関係があるかどうかは気にしません。
Brent Yorgey 2012年

回答:


42

モナドと同型であるこのモナドを考えてみましょう(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

Maybeモナドで構成します。

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

私はそれBadがモナドになることはできないと主張します。


部分的な証明:

fmapを満たすことを定義する唯一の方法がありますfmap id = id

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

モナドの法則を思い出してください。

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

の定義にはreturn xB Nothingまたはの2つの選択肢がありますB (Just (P x x))x(1)と(2)から戻る希望を持っているためには、捨てることができないのは明らかなxので、2番目のオプションを選択する必要があります。

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

それは去りjoinます。可能な入力はごくわずかであるため、それぞれについてケースを作成できます。

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

出力のタイプはBad a、であるため、オプションはB NothingまたはB (Just (P y1 y2))でありy1y2から選択する必要がありますx1 ... x4

(A)と(B)の場合、タイプの値がないaためB Nothing、どちらの場合も戻る必要があります。

ケース(E)は、(1)および(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))

戻すために、B (Just (P y1 y2))ケース(E)で、この手段は、私たちは選択する必要がありますy1いずれかからx1x3、とy2のいずれかからx2x4

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

同様に、これは我々が選択しなければならないと言っているy1のいずれかからx1x2、およびy2いずれかからx3x4。この2つを組み合わせて、(E)の右側がである必要があると判断しますB (Just (P x1 x4))

これまでのところすべて問題ありませんが、(C)と(D)の右側を入力しようとすると問題が発生します。

それぞれに5つの可能な右側があり、どの組み合わせも機能しません。私はまだこれについて良い議論をしていませんが、すべての組み合わせを徹底的にテストするプログラムがあります:

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

ありがとう、私は確信しています!それはあなたの証明を単純化する方法があるかどうか私に疑問に思いますが。
Brent Yorgey 2012年

1
@BrentYorgey:ケース(C)と(D)の問題は、を定義しようとしたときの問題と非常によく似ているように思われるので、あるべきだと思いますswap :: Pair (Maybe a) -> Maybe (Pair a)
hammar 2012年

11
つまり、モナドは情報を破棄することが許可されており、モナドがそれ自体にネストされているだけであれば問題ありません。ただし、情報保存モナドと情報ドロップモナドがある場合、情報保存モナドは独自のモナド法を満たすために情報を保持する必要がありますが、2つのドロップ情報を組み合わせます。したがって、任意のモナドを組み合わせることができません。(これが、関連情報をドロップしないことを保証するトラバース可能なモナドが必要な理由です。それらは任意に構成可能です。)直感に感謝します!
xanthir 2015

@Xanthir Composed(Maybe a, Maybe a)は、1つの順序でのみMaybe (a, a)機能します。モナドです(2つのモナドの積であるため)が、モナドではありません。またMaybe (a,a)、明示的な計算によって、それがモナドではないことも確認しました。
winitzki 2018

なぜMaybe (a, a)モナドではないのかを示す心?多分とタプルはどちらもトラバース可能であり、任意の順序で作成可能である必要があります。この特定の例について話している他のSOの質問もあります。
xanthir 2018

38

小さな具体的な反例として、ターミナルモナドを考えてみましょう。

data Thud x = Thud

そしてreturn>>=ただ行くだけThudで、法律は自明に成り立ちます。

次に、Boolのライターモナドも作成します(たとえば、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

えーと、作曲が必要です

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

今定義してみてください...

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

パラメトリシティは???、に有用な方法で依存することはできないことを示しているxため、定数でなければなりません。結果として、join . returnは必然的に定数関数でもあり、したがって法則

join . return = id

の定義が何であれ失敗する必要がありjoinreturn私たちは選択します。


3
カルロのHamalainenに私が役に立ったと評価してきた上記の回答の追加、非常に明確かつ詳細な分析があるブログ:carlo-hamalainen.net/blog/2014/1/2/...
paluh

34

排中律の構築

(->) rすべてのためのモナドであるrEither eすべてのためのモナドですe。それらの構成((->) r内側、Either e外側)を定義しましょう:

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

私は、もしComp r eすべてのモナドでreあるならば、私たちは排除された中間の法則を実現することができた主張します。これは、関数型言語の型システムの基礎となる直観主義論理では不可能です(排中律を持つことは、call / cc演算子を持つことと同等です)。

Compモナドだとしましょう。次に、

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

定義できるように

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

(これは、ブレントが言及しているswap紙の構成モナド、セクション4.3からの関数であり、ニュータイプの(デ)コンストラクターが追加されているだけです。それがどのようなプロパティを持っているかは気にしないことに注意してください。唯一重要なことは、定義可能で合計であるということです。 。)

さあ、設定しましょう

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

スワップを専門にします r = be = ba = False

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

結論:にもかかわらず、(->) rEither rモナドあり、その組成はComp r rすることはできません。

注:これはReaderT、およびのEitherT定義方法にも反映されます。両方とも ReaderT r (Either e)EitherT e (Reader r)は同型r -> Either e aです!デュアルのモナドを定義する方法はありませんEither e (r -> a)


IOアクションのエスケープ

同じ静脈に関係する多くの例があります IOIO何らかの形で脱出につながる。例えば:

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)

さあ、

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)

このプログラムを実行するとどうなりますか?2つの可能性があります:

  1. コンソールから読み取った後、「First」または「Second」が出力されinputます。これは、アクションの順序が逆になり、からのアクションがfooが純粋なにエスケープされたfます。
  2. またはswap(したがってjoinIOアクションを破棄し、「First」も「Second」も出力されません。しかし、これはそれjoinが法律に違反していることを意味します

    join . return = id
    

    場合は理由joinスローIO離れて行動をし、

    foo ≠ (join . return) foo
    

他の同様のIO+モナドの組み合わせは構築につながります

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

それらのjoin実装は、e脱出を許可するか、IOそれを破棄して別のものに置き換え、法律に違反する必要があります。


(「ap」は「fmap、pure、apが正規の定義である」のタイプミスだと思います(<*>代わりにそうする必要があります)、編集しようとしましたが、編集が短すぎると言われました。)---明確ではありませんの定義があるということは、の定義をjoin意味するということですswap。それを拡張していただけませんか?ブレントによって参照さの紙から行くためにそれを意味するようだjoinswap、我々は次の仮定が必要になります joinM . fmapM join = join . joinMし、 join . fmap (fmapM joinN ) = fmapM joinN . join どこjoinM =参加::などM、
ラファエル・カエターノ

1
@RafaelCaetanoタイプミスをありがとう、私はそれを修正しました(そしてまたswap、紙に一致するように関数の名前を変更しました)。私は今まで論文をチェックしていませんでしたが、その通り、swap<->を定義するにはJ(1)とJ(2)が必要なようですjoin。これはおそらく私の証明の弱点であり、私はそれについてもっと考えます(多分それがそうであるという事実から何かを得ることが可能であるでしょうApplicative)。
Petr

@RafaelCaetanoしかし、証明はまだ有効だと思います。もしあれば、上記の定義を使用してjoin定義できますswap :: (Int -> Maybe a) -> Maybe (Int -> a)(これがどの法則をswap満たすかに関係なく)。そのswapような振る舞いはどうでしょうか?何を持ってInt、それが返却しなければならないので、その引数に渡すことは何もありませんNothingすべての入力のために。私たちはのための矛盾を得ることができると信じてjoin定義する必要なしのモナドの法則をjoinからswapバック。確認してお知らせします。
Petr

@Petr:現状では、これは私が探している証拠ではないというラファエルに同意しますが、あなたが言及した線に沿って修正できるかどうかも知りたいです。
Brent Yorgey 2012年

1
@PetrPudlákうわー、とてもいい!はい、今は完全に購入しています。これらはいくつかの本当に興味深い洞察です。モナドの法則をまったく参照せずに、単にスワップを構築できることが矛盾につながる可能性があるとは思いもしませんでした。複数の回答を受け入れることができれば、これも受け入れるでしょう。
Brent Yorgey 2012年

4

リンクはこのデータ型を参照しているので、特定の実装を選択してみましょう。 data A3 a = A3 (A1 (A2 a))

勝手に選びA1 = IO, A2 = []ます。またnewtype、楽しみのために、それをaにして、特に先のとがった名前を付けます。

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

そのタイプの任意のアクションを考え出し、2つの異なるが等しい方法で実行してみましょう。

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

ご覧のとおり、これは結合法則に違反します。 ∀x y z. (x >=> y) >=> z == x >=> (y >=> z)

可換モナドであるListT m場合、それmはモナドにすぎないことが判明しました。これにより、大規模なカテゴリのモナドが[]「2つの任意のモナドを構成するとモナドが生成される」という普遍的なルールに違反します。

参照:https//stackoverflow.com/a/12617918/1769569


11
これは、可能な定義が機能ListTないことを示すのではなく、1つの特定の定義がすべての場合にモナドを生成できないことを示しているだけだと思います。
CA McCann

私はする必要はありません。「これすべてのために」の否定は「反例が存在する」です。尋ねられた質問は、「すべてのモナドにとって、それらの構成はモナドを形成する」でした。それ自体がモナドであるが、構成できないタイプの組み合わせを示しました。
hpc 2012年

11
@hpcですが、2つのモナドの構成は、それらのタイプの構成よりも多くなっています。また、操作が必要です。ブレントの質問の私の解釈は、操作の実装を導き出すための系統的な方法がない可能性があるということです-彼はさらに強力なものを探しています、いくつかの構成には法律を満たす操作がないかどうか機械的に導出可能かどうか。それは理にかなっていますか?
luqui 2012年

はい、ルキはそれを正しく持っています。私の元の質問が明確でなかったら申し訳ありません。
Brent Yorgey 2012年

この答えに本当に欠けているのは、のMonadインスタンスとListT、他に何もないというデモンストレーションです。声明は「これすべてのために、それが存在する」であり、したがって否定は「これがすべてのために存在する」である
ベンミルウッド2012年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.