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

Pet*_*lák 39 io monads haskell functional-programming

我已经看到提到IO不满足monad定律,但我没有找到一个简单的例子来证明这一点.有人知道一个例子吗?谢谢.

编辑:正如ertes和nm指出的那样,使用seq有点非法,因为它可以使任何monad失败的法律(结合undefined).由于undefined可以将其视为非终止计算,因此使用它完全没问题.

所以修改后的问题是:任何人都知道一个例子表明IO未能满足monad法则,而不使用seq(或者,IO如果seq不允许,可能会出现符合法律规定的证据?)

小智 41

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

在Kleisli类别中monad引起的,return是身份态射和(<=<)构成.所以return必须是一个身份(<=<):

return <=< x = x
Run Code Online (Sandbox Code Playgroud)

使用seq偶数身份和可能无法成为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
Run Code Online (Sandbox Code Playgroud)

  • 我真的希望你注册这个网站 - 很高兴在一个地方看到你的所有答案! (6认同)
  • 这不是真的.根据monad定律(用`>> =`定义),第一个表达式相当于`seq(\ x - > undefined x :: a - > Identity b)()`而不是`seq (undefined :: a - > Identity b)()`.我知道`(\ x - > fx)`相当于`f`,但这不是GHC对待`undefined'的方式. (4认同)

Dan*_*her 25

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
Run Code Online (Sandbox Code Playgroud)

它被实现为(严格)状态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
Run Code Online (Sandbox Code Playgroud)

忽略newtype包装器,这意味着return x >>= f成为\s -> (f x) s.(可能)区分的唯一方法f xseq.(并且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
Run Code Online (Sandbox Code Playgroud)

再次忽略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
Run Code Online (Sandbox Code Playgroud)

现在,我们一般都有

case (case e of                    case e of
        pat1 -> ex1) of       ?      pat1 -> case ex1 of
  pat2 -> ex2                                  pat2 -> ex2
Run Code Online (Sandbox Code Playgroud)

根据语言报告的方程3.17.3.(a),因此该法律不仅包含模数seq.

总结,IO满足monad法则,除了seq可以区分undefined和的事实\s -> undefined s.这同样适用于State[T],Reader[T],(->) a,和任何其它单子包裹的函数类型.


Chr*_*lor 8

其中一个monad法则是

m >>= return ? m
Run Code Online (Sandbox Code Playgroud)

让我们在GHCi中试一试:

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

Prelude> seq ( undefined :: IO () ) "hello, world"
*** Exception: Prelude.undefined
Run Code Online (Sandbox Code Playgroud)

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

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

Prelude> seq ( undefined :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined    
Run Code Online (Sandbox Code Playgroud)

这两个表达式是相同的 - 因此Maybe不排除在这个例子中成为monad.

如果有人有一个不依赖于使用的例子,seq或者undefined我有兴趣看到它.

  • 请记住,"IO"*可以*区分不同的底部,因此在特别考虑"IO"时,这些示例是完全合理的. (5认同)
  • @AndrewC依赖于你区分不同的底部.对于类型系统,所有底部看起来都一样.我同意,一个没有未定义的例子会更令人满意. (4认同)
  • 我真的不认为这是"IO"不遵守monad法律的原因; 我_don't_认为`undefined`很好.您可以使用undefined来证明加法不是可交换的:`(错误"第一个"+错误"第二个")+ 7`表现不同于`(错误"第二个"+错误"第一个")+ 7`.休克!Haskell的补充不是可交换的!我们需要一个_doesn't_使用`undefined`或`error`的例子. (3认同)
  • `( undefined &gt;&gt;= return :: IO () )` 不是*底部。 (2认同)