5

在深入研究 monad 之后,我了解到它们是一个通用概念,允许在某些上下文(失败、非确定性、状态等)内链接计算,并且它们背后没有魔法。

即使不是魔法,但 IO monad 仍然感觉很特别。

  • 你不能像其他 monad 一样逃避 IO monad
  • IO 动作只能由main函数运行
  • IO 始终位于 monad 转换器链的底部
  • IO monad 的实现尚不清楚,源代码显示了一些 Haskell 内部结构

以上几点的原因是什么?是什么让 IO 如此特别?

更新:纯代码评估顺序无关紧要。但是在做 IO 时确实很重要(我们想在阅读之前保存客户)。据我了解,IO monad 为我们提供了这样的排序保证。它是一般 monad 的属性还是 IO monad 特有的属性?

4

2 回答 2

7

你不能像其他 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是一个巨大的麻烦制造者(最好始终避免)。

于 2016-04-13T12:01:02.943 回答
2

ST可以对monad 或可以说是monad做出类似的陈述STM(尽管您实际上可以在IO.

基本上像 Reader monad、Error monad、Writer monad 等都是纯代码。STandIO单子是唯一真正做不纯的事情(状态突变等)的单子,所以它们在纯 Haskell 中是不可定义的。它们必须在某个地方“硬连线”到编译器中。

于 2016-04-13T11:31:28.893 回答