输出是一种具有效果的计算。因此,将其封装到单子中是合理的。但输入是一种具有上下文敏感性的计算。因此,将其封装到共单子中更加合理。
然而,在Haskell中,输入和输出都被封装在IO
单子中。为什么?
输出是一种具有效果的计算。因此,将其封装到单子中是合理的。但输入是一种具有上下文敏感性的计算。因此,将其封装到共单子中更加合理。
然而,在Haskell中,输入和输出都被封装在IO
单子中。为什么?
一个共函子(comonad)有一个extract :: w a -> a
方法,但对于IO
,它不能(合理地)被实现。
在共函子中,输入实际上并不具有上下文敏感性。共函子中的“上下文敏感性”指的是它对于数据结构中更大的上下文敏感。例如,列表焦点(list zipper)就像一个列表,带有一些额外的有关我们在给定时刻“所处位置”的信息。 IO
数据结构实际上没有任何真正的结构,因此它没有要敏感的上下文。
Monad
结构允许我们使用 >>=
操作从内部访问 IO
类型的输入,所以事情都可以进行得很好。
另外,请注意,“具有效果”和“上下文敏感”这些术语有点非正式,因此可能不会对所有单子和共函子的所有示例完全有意义:函数单子真的“具有效果”吗? (,) e
共函子真的“上下文敏感”吗?
顺便说一句,了解单子和共函子工作原理最好的方法是通过使用它们来获得经验(对于许多其他事情也是如此)。不幸的是,没有真正好的方式可以用像“具有效果”或“上下文敏感”这样的短语来总结它们,以便让您了解它们实际上是如何工作的。这些短语可能有所帮助,但重要的是记住它们的限制。
另外,了解一个类型类的最佳方法是了解它的实例(尝试浏览所有提供的实例并弄清楚它们)。一旦做到这一点,您就可以查看该类型类如何与所有实例相连接。这将为您提供有关类型类“含义”的很好直觉。我还应指出,至少在浏览其实例时,将可能为类型类提供的任何法则记在脑后是个好主意。
extract :: w a -> a
意味着我们可以使用纯计算来计算a
,而不产生任何副作用。另一方面,当我们想要使用副作用生成一些值时,我们使用IO
。即使是看似仅限于输入的事情也具有副作用。例如:从网络接收数据,从文件中读取字节并推进流位置,从数据库中读取数据可能具有诸如打开网络连接、记录访问、激活某些触发器等副作用。因此,comonad不是IO
的适当抽象。