Haskell 一个简单的例子显示IO不';你不能满足单子定律吗?

Haskell 一个简单的例子显示IO不';你不能满足单子定律吗?,haskell,io,functional-programming,monads,Haskell,Io,Functional Programming,Monads,我已经看到有人提到,IO不满足monad定律,但我没有找到一个简单的例子来说明这一点。有人知道一个例子吗?谢谢 编辑:正如和n.m.指出的那样,使用seq有点违法,因为它会使任何单子不符合法律(与未定义的结合使用)。由于未定义的可能被视为一个非终止计算,因此使用它是完全正确的 因此,修改后的问题是:有人知道一个例子,表明IO没有使用seq,就不能满足monad定律吗?(或者如果seq不被允许,也许可以证明IO确实满足定律?monad定律之一是这样的 m >>= return ≡ m

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

编辑:正如和n.m.指出的那样,使用
seq
有点违法,因为它会使任何单子不符合法律(与
未定义的
结合使用)。由于
未定义的
可能被视为一个非终止计算,因此使用它是完全正确的


因此,修改后的问题是:有人知道一个例子,表明
IO
没有使用
seq
,就不能满足monad定律吗?
(或者如果
seq
不被允许,也许可以证明
IO
确实满足定律?

monad定律之一是这样的

m >>= return ≡ m
让我们在GHCi中尝试一下:

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

Prelude> seq ( undefined :: IO () ) "hello, world"
*** Exception: Prelude.undefined
因此
undefined>>=return
undefined
不同,因此
IO
不是单子。另一方面,如果我们对
单子尝试同样的方法,则可能是
单子:

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

Prelude> seq ( undefined :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined    
这两个表达式是相同的-因此本例不排除
可能是单子


如果有人有一个不依赖于使用
seq
未定义的
的例子,我很想看看。

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


在Kleisli范畴中,单子产生了,
return
是身份态射,
(tl;dr-upfront:
seq
是唯一的方法

由于标准中没有规定
IO
的实现,我们只能看具体的实现。如果我们看一下GHC的实现,因为它可以从源代码获得(可能是
IO
的一些幕后特殊处理导致了对monad法律的违反,但我不知道有这种情况发生)

它被实现为一个(严格的)状态monad。因此,任何违反monad法则的行为都会被
Control.monad.state[.strict]
造成

让我们看看单子定律,看看在
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
忽略新类型包装器,这意味着
返回x>=f
将变成
\s->(fx)s
。唯一(可能)将其与
fx
区分开来的方法是
seq
(并且
seq
只能在
fx时才能将其区分开来≡ 未定义

再次忽略新类型包装,
k
被替换为
\s->ks
,这同样只能通过
seq
区分,并且只有当
k≡ 未定义

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

总之,
IO
满足monad定律,除了
seq
可以区分
未定义的
\s>未定义的
之外。对于
状态[T]
读卡器[T]
(>)a
,以及包装函数类型的任何其他monad也是如此

m >>= return ≡ m
已损坏:

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: IO ()
内存混乱并增加计算时间,而

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


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

未定义的
很好,但是
seq
是Haskell土地上的非法外国人。根据自由定理,你不能拥有这样的功能!我真的不认为这是不遵守Monad法则的理由s、 我不认为
未定义
是好的。你可以用未定义来证明加法是不可交换的:
(error“first”+error“second”)+7
的行为与
(error“second”+error“first”)不同+7
.Shock!Haskell的加法不是可交换的!我们需要一个不使用
未定义
错误
@AndrewC的示例,它依赖于您区分不同的底部。对于类型系统,所有底部看起来都一样。不过,我同意,一个没有未定义的示例会更令人满意。请记住t
IO
可以区分不同的底部,因此当具体考虑
IO
时,这样的例子是完全合理的。
(未定义>>=return::IO())
不是最底层。我真的希望你注册该网站-在一个地方看到你所有的答案将是非常棒的!@ChrisTaylor:如果你愿意,你可以看到。有一次我在
[haskell]中寻找可能的重复帐户
标记并标记一些,以便在至少有一个帐户是注册帐户时进行合并,但…:t这不完全正确。根据monad法则(根据
>=
定义),第一个表达式相当于
seq(\x->undefined x::a->Identity b)(
而不是
seq(undefined::a->Identity b)()<代码> >(\x-> fx)相当于<代码> f>代码>,但这不是GHC对待“<代码>未定义< /COD> > S.当我考虑(返回身份B)和(未定义::A:>身份B)我的反应不同,但我们不说这意味着它们不是同一个态射。为什么我们会担心seq的反应不同?你没有用seq组合它们,所以你已经在范畴理论之外了。难道你不能用IO通过外部手段编辑运行程序的内存并强制打破一条定律吗?@KendallHopkins好主意。Ano另一个想法(也许更简单)可能是利用Haskell的懒惰IO的已知问题。不使用
seq
?@gawi是否有实际的兴趣来展示它?兴趣主要是理论性的,因为任何这样的例子都非常罕见
m >>= return ≡ m
sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: IO ()
sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: Maybe ()