web-dev-qa-db-ja.com

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

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

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

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

80
Brent Yorgey

(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 id = idを満たすfmapを定義する唯一の方法があります。

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 xの定義には、B NothingまたはB (Just (P x x))の2つの選択肢があります。 (1)と(2)からxを返すことを期待するには、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))のみです。ここで、y1y2x1 ... 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))

(E)の場合にB (Just (P y1 y2))を返すには、y1またはx1からx3を選択し、y2またはx2から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))

同様に、これは、y1またはx1からx2を選択し、y2またはx3からx4を選択する必要があることを示しています。この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'')
40
hammar

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

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の定義が何であれ失敗する必要があります。

37
pigworker

排中律の構築

_(->) r_はすべてのrのモナドであり、_Either 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、newtypeの(de)コンストラクターが追加されているだけです。それがどんな特性を持っているかを気にします、唯一の重要なことはそれが定義可能で合計であるということです。)

さあ、設定しましょう

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

_r = b_、_e = b_、_a = False_のスワップを専門にします。

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

結論:_(->) r_と_Either r_はモナドですが、それらの構成_Comp r r_はできません。

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


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

IOを含み、何らかの形でIOをエスケープすることにつながる、同じ静脈内の多くの例があります。例えば:

_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」が出力されますafterコンソールからinputを読み取ります。これは、アクションのシーケンスが反転されたことを意味しますそしてfooからのアクションが純粋なfにエスケープされたこと。
  2. または、swap(したがって、join)はIOアクションを破棄し、「First」も「Second」も出力されません。しかし、これはjoinが法律に違反していることを意味します

    _join . return = id
    _

    joinIOアクションを破棄した場合、

    _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の実装はeIOから脱出できるようにするか、それを破棄して別のものに置き換え、法律に違反する必要があります。

33
Petr Pudlák

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

_A1 = IO, A2 = []_を任意に選びます。また、楽しみのために、これをnewtypeにし、特に先のとがった名前を付けます。

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)

m可換モナドである場合、_ListT m_はモナドにすぎません。これにより、大きなカテゴリのモナドが_[]_で構成されなくなり、「2つの任意のモナドを構成するとモナドが生成される」という普遍的なルールに違反します。

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

4
hpc

モナドは作曲しません。一般的な方法ではありません-モナドを構成する一般的な方法はありません。 https://www.slideshare.net/pjschwarz/monads-do-not-compose を参照してください

0
Philip Schwarz