观点 A 有点奇怪——一般来说,一个抽象不会比其他抽象更强大和更通用;两人不和。拥有“更多权力”意味着更多地了解你正在使用的东西的结构,这意味着更多的限制。在一种极端情况下,您确切地知道您正在使用哪种类型。这是非常强大的;您可以对其应用任何有效的功能。另一方面,它至少也不是通用的:用这种假设编写的代码只适用于那种类型!在另一个极端,您对您的类型一无所知(例如,拥有一个类型变量a
)。这是非常普遍的,适用于每个类型,但也不强大,因为您根本没有足够的信息来做任何事情!
Functor
一个更植根于真实代码的例子是和之间的区别Applicative
。这里,Functor
更一般——严格来说,Functor
s 比Applicative
s 更多的类型,因为everyApplicative
也是 aFunctor
但反之则不然。但是,由于Applicative
具有更多的结构,因此它的功能更强大。使用Functor
,您只能将单参数函数映射到您的类型;使用Applicative
,您可以映射任意数量参数的函数。再说一遍:一个更通用,另一个更强大。
那么它是哪一个?箭头是否比单子更强大或更通用?这是一个比比较函子、应用函子和单子更难的问题,因为箭头是一种非常不同的野兽。他们甚至有不同的种类:monads 等人有 kind* -> *
而箭头有 kind * -> * -> *
。令人高兴的是,事实证明我们可以用 applicative functors/monads 来识别箭头,因此我们实际上可以有意义地回答这个问题:箭头比 monads 更通用,因此功能更弱。给定任何单子,我们可以从中构造一个箭头,但我们不能为每个箭头构造一个单子。
基本思路如下:
instance Monad m => Category (a -> m b) where
id = return
(f . g) x = g x >>= f
instance Monad m => Arrow (a -> m b) where
arr f = return . f
first f (x, y) = f x >>= \ x' -> return (x', y)
但是,由于我们有一个箭头实例a -> b
,我们必须将其包装a -> m b
到newtype
实际代码中。这newtype
被称为Klesli
(因为Klesli 类别)。
但是,我们不能走另一条路——没有构造可以Monad
从任何 Arrow
. 发生这种情况是因为Arrow
计算不能根据流经它的值来改变其结构,而 monad 可以。解决这个问题的唯一方法是通过一些额外的原始函数来为你的箭头抽象增加力量;这正是这样ArrowApply
做的。
>>>
箭头运算符是函数的泛化,.
因此具有相同的一般限制。>>=
另一方面,更像是对函数应用的概括。注意类型:对于>>>
,两边都是箭头;对于>>=
,第一个参数是一个值 ( m a
),第二个参数是一个函数。此外,结果>>>
是另一个箭头,其中的结果>>=
是一个值。由于箭头只有>>>
但没有等价于 的概念>>=
,因此您一般不能将它们“应用”到参数上——您只能构造箭头管道。实际的应用/运行功能必须特定于任何给定的箭头。另一方面,单子被定义为>>=
因此默认情况下带有一些应用程序的概念。
ArrowApply
只是用 扩展箭头app
,这是应用的一般概念。让我们想象一下正常的功能应用:
apply :: (b -> c) -> b -> c
apply f x = f x
我们可以 uncurry 得到:
apply :: ((b -> c), b) -> c
箭头泛化函数的方式基本上是用->
变量 ( a
) 替换。让我们通过用中缀apply
替换两个出现来做到这一点:->
a
apply :: (b `a` c, b) `a` c
我们仍然可以看到与 的第一个版本相同的结构apply
,只是 uncurried 和 with`a`
而不是->
。现在,如果我们去掉反引号并加上a
前缀,我们就得到了 的签名app
:
app :: a (a b c, b) c
因此,我们看到如何ArrowApply
将一些应用概念添加到箭头。这是与 的平行>>=
,它是单子的应用概念(或者,特别是形状的函数a -> m b
)。这是足够的附加结构来从箭头构建一个 monad,因此ArrowApply
同构于Monad
.
我们为什么要使用这些?老实说,我认为我们不会。箭头被高估了,所以坚持使用 monads 和 applicative functors。