你不能像其他 monad 一样逃避 IO monad
我不确定你所说的“逃跑”是什么意思。如果你的意思是,以某种方式解开一元值的内部表示(例如列表 -> 单元格序列)——这是一个实现细节。事实上,你可以在纯 Haskell中定义一个IO
仿真——基本上只是State
大量全球可用的数据。那将具有 的所有语义IO
,但实际上并未与现实世界交互,而只是对其进行模拟。
如果您的意思是,您可以从 monad 中“提取值”——不,这通常是不可能的,即使对于大多数纯 haskell monad 也是如此。例如,您无法从Maybe a
(could be Nothing
) 或Reader b a
(如果b
无人居住怎么办?)
IO
动作只能由main
函数运行
嗯,从某种意义上说,一切都只能由main
函数来运行。没有以某种方式调用的代码main
只会坐在那里,您可以在undefined
不更改任何内容的情况下替换它。
IO 始终位于 monad 转换器链的底部
没错,但对于 eg 也是如此ST
。
monad 的实现IO
尚不清楚,源代码显示了一些 Haskell 内部结构
再说一遍:实现只是一个实现细节。实现的复杂性IO
实际上与高度优化有很大关系。对于专门的纯单子(例如attoparsec)也是如此。
正如我已经说过的),更简单的实现是可能的,它们只是不如成熟的优化现实世界IO
类型有用。
幸运的是,实现并不需要真正打扰您。内部可能不清楚,但外部,实际的一元接口,非常简单。IO
纯代码评估顺序无关紧要
首先——评估顺序在纯代码中很重要!
Prelude> take 10 $ foldr (\h t -> h `seq` (h:t)) [] [0..]
[0,1,2,3,4,5,6,7,8,9]
Prelude> take 10 $ foldr (\h t -> t `seq` (h:t)) [] [0..]
^CInterrupted.
但实际上,由于纯代码评估顺序错误,您永远不会得到错误的非⊥结果。不过,这实际上不适用于重新排序一元动作(IO
或其他方式),因为更改序列顺序会更改结果动作的实际结构,而不仅仅是运行时将用于构造此结构的评估顺序。
例如(列表单子):
Prelude> [1,2,3] >>= \e -> [10,20,30] >>= \z -> [e+z]
[11,21,31,12,22,32,13,23,33]
Prelude> [10,20,30] >>= \z -> [1,2,3] >>= \e -> [e+z]
[11,12,13,21,22,23,31,32,33]
所有这一切,当然IO
很特别,确实我认为有些人不愿称它为单子(有点不清楚它实际上应该意味着什么IO
来满足单子定律)。特别是,惰性 IO是一个巨大的麻烦制造者(最好始终避免)。