86

众所周知,应用函子在组合下是封闭的,但单子不是。然而,我一直很难找到一个具体的反例来证明单子并不总是组成。

这个答案给出[String -> a]了一个非单子的例子。在玩了一会儿之后,我直觉地相信它,但这个答案只是说“无法实现加入”而没有真正给出任何理由。我想要更正式的东西。当然 type 有很多函数[String -> [String -> a]] -> [String -> a];必须证明任何这样的功能都不一定满足单子定律。

任何例子(附有证明)都可以;我不一定要特别寻找上述示例的证明。

4

5 回答 5

42

考虑这个与 monad 同构的(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

并用Maybemonad 组合它:

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 x,我们有两种选择:B NothingB (Just (P x x))。很明显,为了有任何x从(1)和(2)返回的希望,我们不能扔掉x,所以我们必须选择第二个选项。

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 NothingB (Just (P y1 y2))where y1y2必须从中选择x1 ... x4

在情况 (A) 和 (B) 中,我们没有 type 的值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) 中返回,这意味着我们必须从 or 中选择y1一个x1x3并且y2x2or中选择一个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))

同样,这表示我们必须从 or 中选择y1一个x1x2并且y2x3or中选择一个x4。结合两者,我们确定 (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'')
于 2012-10-24T03:54:21.480 回答
38

对于一个小的具体反例,请考虑终端单子。

data Thud x = Thud

returnand >>=just go Thud,并且法律是微不足道的。

现在让我们也有 Bool 的 writer monad(比如说,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

于 2012-11-03T12:43:18.307 回答
34

构造排中

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

(这只是Brent 提到swap的论文Composing monads中的函数,第 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

结论:即使(->) rEither r是单子,它们的组成Comp r r也不能。

注意:这也反映在如何定义ReaderTEitherT定义上。和 同构于! 没有办法为 dual 定义 monad 。ReaderT r (Either e)EitherT e (Reader r)r -> Either e aEither 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)

当这个程序运行时会发生什么?有两种可能:

  1. “First” 或 “Second”在我们从控制台读取input打印出来,这意味着动作的顺序被颠倒了,动作从foo转义到 pure f
  2. swap(因此join)放弃该IO动作,并且从未打印过“第一”或“第二”。但这意味着join违反法律

    join . return = id
    

    因为如果joinIO动作扔掉,那么

    foo ≠ (join . return) foo
    

其他类似IO的 + monad 组合导致构建

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要么他们必须将其丢弃并用其他东西代替,这违反了法律。

于 2012-10-24T10:25:22.203 回答
4

您的链接引用了此数据类型,因此让我们尝试选择一些特定的实现:data A3 a = A3 (A1 (A2 a))

我会随意挑选A1 = IO, A2 = []。为了好玩,我们还将它命名为 anewtype并给它一个特别尖的名字:

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

让我们在该类型中提出一些任意操作,并以两种不同但相同的方式运行它:

λ> 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可交换单子,则只是单子。这可以防止一大类 monad 与 组合[],这打破了“组合两个任意 monad 产生一个 monad”的普遍规则。

另请参阅:https ://stackoverflow.com/a/12617918/1769569

于 2012-10-23T21:03:18.930 回答
-1

单子不构成。不是以通用方式 - 没有构成单子的通用方式。见https://www.slideshare.net/pjschwarz/monads-do-not-compose

于 2019-04-24T21:54:43.797 回答