一个简单的例子表明IO不符合monad法则?

时间:2012-09-27 08:59:05

标签: haskell io functional-programming monads

我已经看到提到IO不符合monad法则,但我没有找到一个简单的例子来证明这一点。有人知道一个例子吗?感谢。

编辑ertes和n.m.指出,使用seq有点违法,因为它可以使任何monad失败的法律(与undefined结合)。由于undefined可能被视为非终止计算,因此使用它是完全正确的。

所以修改后的问题是:有人知道一个例子,显示IO未能满足monad法则,而不使用seq(或者可能是{{}的证据如果不允许IO,1}}是否满足法律?)

4 个答案:

答案 0 :(得分:41)

如果排除奇怪的seq组合子,Haskell中的所有monad都只是monad。 IO也是如此。由于seq实际上不是常规函数但涉及魔法,因此您必须将其排除在检查monad定律之外,原因与您必须排除unsafePerformIO相同。使用seq可以证明所有monad错误,如下所示。

在Kleisli类别中monad引起,return是身份态射,(<=<)是构成。因此return必须是(<=<)的身份:

return <=< x = x

使用seq甚至Identity和Maybe都不能成为monad:

seq (return <=< undefined :: a -> Identity b) () = ()
seq (undefined            :: a -> Identity b) () = undefined

seq (return <=< undefined :: a -> Maybe b) () = ()
seq (undefined            :: a -> Maybe b) () = undefined

答案 1 :(得分:24)

tl; dr beforefront:seq是唯一的方法。

由于标准没有规定IO的实施,我们只能看具体实施。如果我们看一下GHC的实现,因为它可以从源头获得(可能是IO的一些幕后特殊处理引入了违反monad法则,但我不知道任何这样的事情),

-- in GHC.Types (ghc-prim)
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

-- in GHC.Base (base)
instance  Monad IO  where
    {-# INLINE return #-}
    {-# INLINE (>>)   #-}
    {-# INLINE (>>=)  #-}
    m >> k    = m >>= \ _ -> k
    return    = returnIO
    (>>=)     = bindIO
    fail s    = failIO s

returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)

bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s

thenIO :: IO a -> IO b -> IO b
thenIO (IO m) k = IO $ \ s -> case m s of (# new_s, _ #) -> unIO k new_s

unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a

它被实现为(严格)状态monad。因此,任何违反monad法律IO的行为都是Control.Monad.State[.Strict]

让我们看一下monad定律,看看IO中发生了什么:

return x >>= f ≡ f x:
return x >>= f = IO $ \s -> case (\t -> (# t, x #)) s of
                              (# new_s, a #) -> unIO (f a) new_s
               = IO $ \s -> case (# s, x #) of
                              (# new_s, a #) -> unIO (f a) new_s
               = IO $ \s -> unIO (f x) s

忽略newtype包装,这意味着return x >>= f变为\s -> (f x) s。 (可能)区别于f x的唯一方法是seq。 (而seq只能在f x ≡ undefined时区分它。)

m >>= return ≡ m:
(IO k) >>= return = IO $ \s -> case k s of
                                 (# new_s, a #) -> unIO (return a) new_s
                  = IO $ \s -> case k s of
                                 (# new_s, a #) -> (\t -> (# t, a #)) new_s
                  = IO $ \s -> case k s of
                                 (# new_s, a #) -> (# new_s, a #)
                  = IO $ \s -> k s

再次忽略newtype包装,k\s -> k s取代,seq只能用k ≡ undefined区分,并且只有m >>= (\x -> g x >>= h) ≡ (m >>= g) >>= h: (IO k) >>= (\x -> g x >>= h) = IO $ \s -> case k s of (# new_s, a #) -> unIO ((\x -> g x >>= h) a) new_s = IO $ \s -> case k s of (# new_s, a #) -> unIO (g a >>= h) new_s = IO $ \s -> case k s of (# new_s, a #) -> (\t -> case unIO (g a) t of (# new_t, b #) -> unIO (h b) new_t) new_s = IO $ \s -> case k s of (# new_s, a #) -> case unIO (g a) new_s of (# new_t, b #) -> unIO (h b) new_t ((IO k) >>= g) >>= h = IO $ \s -> case (\t -> case k t of (# new_s, a #) -> unIO (g a) new_s) s of (# new_t, b #) -> unIO (h b) new_t = IO $ \s -> case (case k s of (# new_s, a #) -> unIO (g a) new_s) of (# new_t, b #) -> unIO (h b) new_t

case (case e of                    case e of
        pat1 -> ex1) of       ≡      pat1 -> case ex1 of
  pat2 -> ex2                                  pat2 -> ex2

现在,我们一般都有

seq

根据语言报告的等式3.17.3.(a),因此该法律不仅以模IO为模。

总结一下,seq符合monad法则,但undefined可区分\s -> undefined sState[T]的事实除外。这同样适用于Reader[T](->) a,{{1}}以及包装函数类型的任何其他monad。

答案 2 :(得分:8)

monad法律之一是

m >>= return ≡ m

让我们在GHCi中尝试一下:

Prelude> seq ( undefined >>= return :: IO () ) "hello, world"
"hello, world"

Prelude> seq ( undefined :: IO () ) "hello, world"
*** Exception: Prelude.undefined

因此undefined >>= returnundefined不同,因此IO不是monad。如果我们为Maybe monad尝试相同的事情,另一方面:

Prelude> seq ( undefined >>= return :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined

Prelude> seq ( undefined :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined    

这两个表达式是相同的 - 所以Maybe不排除在这个例子中成为monad。

如果有人有一个不依赖于使用sequndefined的示例,我会有兴趣看到它。

答案 3 :(得分:3)

m >>= return ≡ m

坏了:

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: IO ()

使内存混乱并增加计算时间,而

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: Maybe ()

没有。

AFAIR有Monad Transformer解决了这个问题;如果我猜对了,那就是Codensity Monad Transformer。