请查看http://www.cs.yale.edu/homes/hudak/CS429F04/AFPLectureNotes.pdf,它解释了箭头在 FRP 中的工作原理。
2 元组用于定义箭头,因为它需要表示带有 2 个参数的箭头函数。
在 FRP 中,常量和变量通常表示为忽略其“输入”的箭头,例如
twelve, eleven :: Arrow f => f p Int
twelve = arr (const 12)
eleven = arr (const 11)
然后将函数应用程序转换为合成 (>>>):
# (6-) 12
arr (6-) <<< twelve
现在我们如何将一个有 2 个参数的函数变成一个箭头?比如
(+) :: Num a => a -> a -> a
由于柯里化,我们可以将其视为返回函数的函数。所以
arr (+) :: (Arrow f, Num a) => f a (a -> a)
现在让我们把它应用到一个常量上
arr (+) -- # f a (a -> a)
<<< twelve -- # f b Int
:: f b (Int -> Int)
+----------+ +-----+ +--------------+
| const 12 |----> | (+) | == | const (+ 12) |
+----------+ +-----+ +--------------+
嘿等等,它不起作用。结果仍然是一个返回函数的箭头,但我们期望类似于f Int Int。 我们注意到 Arrow 中的柯里化失败了,因为只允许合成。因此我们必须先取消柯里化函数
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry (+) :: Num a => (a, a) -> a
然后我们有箭头
(arr.uncurry) (+) :: (Num a, Arrow f) => f (a, a) a
二元组就是因为这个而产生的。那么就需要像&&&这样的一堆函数来处理这些2元组。
(&&&) :: f a b -> f a d -> f a (b, d)
那么添加就可以正确执行了。
(arr.uncurry) (+) -- # f (a, a) a
<<< twelve -- # f b Int
&&& eleven -- # f b Int
:: f b a
+--------+
|const 12|-----.
+--------+ | +-----+ +----------+
&&&====> | (+) | == | const 23 |
+--------+ | +-----+ +----------+
|const 11|-----'
+--------+
(现在,为什么我们不需要像 &&&& 这样的东西来为具有 3 个参数的函数提供 3 元组?因为可以使用 ((a,b),c) 代替。)
编辑:从 John Hughes 的原始论文 Generalising Monads to Arrows 中,将原因描述为
4.1 箭头和对
然而,即使在单子的情况下,运算符 return 和 >>= 是我们开始编写有用代码所需的全部,但对于箭头,类似的运算符 arr 和 >>> 是不够的。甚至我们之前看到的简单的一元加法函数
add :: Monad m => m Int -> m Int -> m Int
add x y = x >>= \u -> (y >>= \v -> return (u + v))
还不能以箭头形式表示。明确地依赖输入,我们看到类似的定义应该采用以下形式
add :: Arrow a => a b Int -> a b Int -> a b Int
add f g = ...
我们必须按顺序组合f 和g。唯一可用的排序运算符是>>>,但f 和g 没有正确的组合类型。实际上,add 函数需要在f 的计算中保存b 类型的输入,以便能够向g 提供相同的输入。同样,f 的结果必须在g 的计算中保存,以便最终可以将两个结果相加并返回。到目前为止介绍的箭头组合器让我们无法在另一个计算中保存一个值,因此我们别无选择,只能引入另一个组合器。