我对函数式编程中使用的单子和箭头的概念非常熟悉。我也明白它们可以用来解决类似的问题。
但是,我仍然对如何选择在任何给定情况下使用哪一个感到有些困惑。
什么时候应该使用单子,什么时候应该使用箭头?
Lindley、Wadler 和 Yallop 有两篇优秀的论文(在 LTU 讨论)。
要理解的最重要的事情是,箭头的东西比单子的东西要多。相反,monad 严格来说比箭头更强大(上面的第二篇论文精确地指定了哪种方式)。
特别是,monad 是配备类型为 apply 函数的箭头(a ~> b, a) ~> b
,其中(~>)
是给定箭头的构造函数。林德利等人。指出这破坏了箭头在术语和命令(或者,如果您愿意,对象和态射)之间保持的细致区别。
应用函子有各种各样的应用,特别是对于那些最好被认为是对流进行操作的东西。事实上,人们可以将箭头看作是对流上的转换器的概念进行概括(即引入一种新语言用于由给定应用函子构造的对象上的态射)。
根据我的经验,因为 monad 模糊了对象和态射之间的区别(即,如果我正确使用这些词,就会产生一个封闭的笛卡尔范畴),那么 monad 中的术语通常比箭头或应用函子(尽管请注意,两者都允许您分别通过arr
和pure
方法注入任意函数)。
因此,如果某些东西没有被赋予单子的特征(即使在概念上它形成了单子),那么它可能会接受更多的检查和优化。序列化也可能更容易。因此,在解析器和电路建模中使用了应用程序和箭头。
以上试图是一般性和描述性的。以下是我的一些自以为是的经验法则。
如果您需要对看起来像状态的东西进行建模,请从 monad 开始。如果您需要对看起来像全局控制流(即异常、延续)的东西进行建模,请从 monad 开始。如果出现与 monad 的功能和通用性相冲突的要求(即,对于哪个 join(join :: m (m a) -> m a)
功能太强大),那么请考虑削弱您正在使用的东西的功能。
如果您需要对流进行建模,并在流上进行转换,尤其是某些特征(尤其是过去和未来的无限视图)应该是不透明的流,那么从应用函子开始。如果您需要对流上的转换属性进行更强的推理,那么请考虑使用箭头。
或者,非常粗略地说,应用程序用于电路的动作,箭头用于电路的结构,而单子用于通用计算效果。
当然还有更多的故事。有关应用程序,请参阅Conal Elliott 关于 FRP 的工作。对于箭头,请参阅HXT XML 解析器库、Yampa FRP 项目、Haskell on a Horse web 框架、Hudak 和 Liu 的经典“用箭头插入空间泄漏”论文等。对于单子,随处可见。当然要注意,仅仅因为某物是单子,这并不意味着应用符号可能不会更清晰和更具表现力。