拥有一个遵守某些规律的抽象可以让你进行泛型编程。您可以在没有任何类型类的情况下进行编程(没有 monad、applicatives、没有相等/排序等),但这会非常不方便,因为您将无法编写利用这些属性的通用代码。
就好像你说你不想要Ord 实例,然后你必须为你可以订购的每种数据类型分别重写Set 的实现。
Arrow 的重点是描述计算
- 接受输入,
- 产生输出,并且
- 中间有一定(效果是口语表达)。
所以箭头A b c 不是函数a -> b。箭头很可能在内部实现为函数,但更复杂,实现Arrow 接口的目的是描述(除其他外)它们是如何构成的。
特别是对于箭头,您拥有arrow notation,它允许您对任何有效的Arrow 使用相同的符号。举个例子,在netwire 包中,Wire 数据类型实现了Arrow,因此您可以使用箭头符号以及所有适用于箭头的实用函数。如果没有实例,你要么必须有一些特定于 netwire 的语法,要么只使用 netwire 提供的函数。
举个例子:State monad 对应的箭头是a -> s -> (b, s)。但是两个这样的函数不使用(.) 组合。您需要描述它们的组成,而这正是 Arrow 所做的。
更新: 可组合性可能有多种概念,但我猜你的意思是类似函数的组合。是的,这种可组合性来自Arrow 的Category 超类,它定义了身份和组合。
Arrow 的另一部分来自Strong profunctor,正如我最近从What's the relationship between profunctors and arrows? 学到的那样(尽管这没有在类型类层次结构中捕获,因为Profunctor 类型类比@987654349 更年轻@)。 Profunctors 允许通过“双方”的纯计算进行修改,请参阅 Profunctor 中的 lmap/rmap/dimap。对于箭头,我们有(<<^) 和(^>>),它们使用arr 和>>> 表示,方法是用来自任一侧的箭头和使用arr 构造的纯箭头组合而成。
最后箭头有强度 wrt (,),被first :: Arrow a => a b c -> a (b, d) (c, d) 捕获。这意味着我们只能在输入的一部分上使用箭头,而传递另一个不变。这允许用“平行线”构建“电路”——没有first,就不可能保存一部分计算的输出并在以后的某个地方使用它。
一个很好的练习是绘制一个表示计算的电路,然后尝试使用Arrow 原语/实用程序,或者使用箭头语法符号来表达它。您会看到first(或***)对此至关重要。
请参阅Arrows can multitask 了解操作的精美图纸。
更多理论背景Arrows are Strong Monads 可能会很有趣(我还没读过)。