haskell 中箭头相对于常规函数有什么优势?他们能做什么而函数不能做什么。函数可以使用 fmap 映射结构。
从更广泛的角度来看,箭头可以让您离开 Hask 并进入其他有待探索的类别。 Kleisli 类别可能是 Haskellers 最熟悉的类别,其次是 Cokleisli。这些是Hask的自然“扩展”:在结果或参数周围添加一个endofunctor,然后你再次得到一个类别如果
Kleisli
:函子是一个单子,所以id ≅ return :: a -> m a
(.) ≅ (<=<) :: (b->m c) -> (a->m b) -> a->m c
CoKleisli
:函子是共子,所以 id ≅ coreturn :: m a -> a
和
(.) :: (m b->c) -> (m a->b) -> m a->c
(为此,你还不需要
Arrow
,只需要Category
。但是一般类别不是很有趣,你通常需要monoidal甚至笛卡尔闭类别,这就是Arrow
的大致内容瞄准。)
但是肯定还有很多其他类别。大多数与 Hask 没有太多关系,并且不能用标准
Arrow
类来表达,主要是因为这些对象具有并非每个 Haskell 类型都满足的特殊属性。实际上,如果您添加约束对象类型的能力,可能性立即变得更广泛。但即使您继续使用标准类,甚至可能只是在 ->
中,带有箭头的自然无点组合风格通常会非常漂亮、简洁,并开辟了思考转换的新方法。
函数只是箭头的一个实例,这就像问“为什么使用 monad 而不仅仅是
Maybe
”。
你可以用箭头做的任何事情当然也可以用函数来完成,因为
Arrow (->)
实例只能谈论函数的一小部分,即 Arrow
类型类中的内容。然而,箭头比普通函数有更多实例,因此我们可以使用相同的函数来操作更复杂的类型。
箭头很好,因为它们可以具有比函数更多的结构,当仅使用
fmap
进行遍历时,我们无法累积效果,比 monad 更具表现力!考虑克莱斯利箭头,
newtype Kleisli m a b = Kleisli {runKleisli :: a -> m b}
当
m
是单子时,这会形成一个箭头。因此,每个 Monad
都形成一个箭头,因此我们可以通过无缝组合 a -> m b
来构建一元计算,并执行诸如此类的各种有用的操作。一些 XML 库使用箭头来抽象从元素到其子元素的函数,并使用它来遍历文档。其他解析器使用箭头(它们最初的目的),尽管现在这似乎不再受Applicative
的青睐。
您希望注意到的一点是,箭头更通用,当我们只谈论箭头时,我们避免重复使用解析器、xml 抓取器和单子函数执行某些操作所需编写的所有代码!
这与选择
Monad
而不是 Maybe
一样,我们失去了一些权力,因为我们不再能够做出特定的语句,但我们得到了更通用的代码作为回报。