从更广阔的角度来看,箭头能够帮助你走出Hask范畴,进入其他有待探索的范畴。Kleisli范畴可能是Haskeller最熟悉的,其次是Cokleisli。这些都是Hask的自然“扩展”:在结果或参数周围添加一个自函子,然后如果满足条件,则再次得到一个范畴。
Kleisli
: the functor is a monad, so id ≅ return :: a -> m a
(.) ≅ (<=<) :: (b->m c) -> (a->m b) -> a->m c
CoKleisli
: the functor is a comonad, so id ≅ coreturn :: m a -> a
and
(.) :: (m b->c) -> (m a->b) -> m a->c
现在你还不需要使用 Arrow
,只需要使用 Category
。但是常规类别并不是非常有趣,通常你需要 幺半范畴(monoidal),甚至是 笛卡尔闭范畴(cartesian closed),这就是 Arrow
大致所针对的。
但是确实有很多其他类别。大多数与 Hask 没有太多关系,不能用标准的 Arrow
类来表示,主要是因为对象具有特殊属性,而不是每个 Haskell 类型都满足。事实上,如果你添加了约束对象类型的能力,可能性立即变得更广泛。但即使你仍然使用标准类别,甚至只是使用 ->
,箭头自然的点无风格组合方式通常会变得非常好、简洁,并开启了新的思考转换的方式。
函数只是箭头的一种实例,就像问“为什么要使用单子而不是只用Maybe
一样”。
当然,你可以用函数来完成任何箭头所能完成的事情,因为Arrow (->)
实例只能涉及到函数中很小的部分,即Arrow
类型类中的内容。然而,箭头有比普通函数更多的实例,所以我们可以使用同样的函数来操作更复杂的类型。
箭头很好用,因为它们可以具有比函数更多的结构。当只用fmap
进行遍历时,我们没有办法累积效果,而箭头比单子更具表现力!考虑Kleisli箭头,
newtype Kleisli m a b = Kleisli {runKleisli :: a -> m b}
m
是一个单子时,它形成了一个箭头。因此,每个 Monad
都形成了一条箭头,我们可以通过无缝组合 a -> m b
来构建单子计算,并做出各种有用的事情。一些 XML 库使用箭头来抽象从元素到其子元素的功能,并使用它来遍历文档。其他解析器使用箭头(它们最初的目的)尽管现在这似乎已经不再受欢迎,而改为使用 Applicative
。Monad
而不是 Maybe
一样,我们失去了一些能力,因为我们不能再做出具体的陈述,但我们得到了更通用的代码。