为什么IO是一个单子而不是余单子?

16

输出是一种具有效果的计算。因此,将其封装到单子中是合理的。但输入是一种具有上下文敏感性的计算。因此,将其封装到共单子中更加合理。

然而,在Haskell中,输入和输出都被封装在IO单子中。为什么?

2个回答

18

一个共函子(comonad)有一个extract :: w a -> a方法,但对于IO,它不能(合理地)被实现。

在共函子中,输入实际上并不具有上下文敏感性。共函子中的“上下文敏感性”指的是它对于数据结构中更大的上下文敏感。例如,列表焦点(list zipper)就像一个列表,带有一些额外的有关我们在给定时刻“所处位置”的信息。 IO 数据结构实际上没有任何真正的结构,因此它没有要敏感的上下文。

Monad 结构允许我们使用 >>= 操作从内部访问 IO 类型的输入,所以事情都可以进行得很好。

另外,请注意,“具有效果”和“上下文敏感”这些术语有点非正式,因此可能不会对所有单子和共函子的所有示例完全有意义:函数单子真的“具有效果”吗? (,) e 共函子真的“上下文敏感”吗?

顺便说一句,了解单子和共函子工作原理最好的方法是通过使用它们来获得经验(对于许多其他事情也是如此)。不幸的是,没有真正好的方式可以用像“具有效果”或“上下文敏感”这样的短语来总结它们,以便让您了解它们实际上是如何工作的。这些短语可能有所帮助,但重要的是记住它们的限制。

另外,了解一个类型类的最佳方法是了解它的实例(尝试浏览所有提供的实例并弄清楚它们)。一旦做到这一点,您就可以查看该类型类如何与所有实例相连接。这将为您提供有关类型类“含义”的很好直觉。我还应指出,至少在浏览其实例时,将可能为类型类提供的任何法则记在脑后是个好主意。


5
comonad的签名extract :: w a -> a意味着我们可以使用纯计算来计算a,而不产生任何副作用。另一方面,当我们想要使用副作用生成一些值时,我们使用IO。即使是看似仅限于输入的事情也具有副作用。例如:从网络接收数据,从文件中读取字节并推进流位置,从数据库中读取数据可能具有诸如打开网络连接、记录访问、激活某些触发器等副作用。因此,comonad不是IO的适当抽象。

好的函数将一个输入映射到一个输出,但有些函数像f x = 1一样忽略它们的输入。许多函子/单子也会忽略它们的容器/上下文。 - aoeu256

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接