请查看
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
现在我们如何将一个有两个参数的函数转换为箭头函数呢?例如
(+) :: Num a => a -> a -> a
由于柯里化,我们可以将其视为返回函数的函数。因此
arr (+) :: (Arrow f, Num a) => f a (a -> a)
现在让我们把它应用到一个常量上
arr (+)
<<< twelve
:: 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-tuple出现了,接下来需要使用像&&&
这样的bunch函数来处理这些2-tuples。
(&&&) :: f a b -> f a d -> f a (b, d)
那么加法就能正确执行。
(arr.uncurry) (+)
<<< twelve
&&& eleven
:: f b a
+--------+
|const 12|-----.
+--------+ | +-----+ +----------+
&&&====> | (+) | == | const 23 |
+--------+ | +-----+ +----------+
|const 11|
+--------+
(现在,为什么我们不需要像 &&&& 这样的东西来处理有 3 个参数的函数?因为我们可以使用 ((a,b),c) 代替。)
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
期间保存下来,以便最终将这两个结果相加并返回。迄今为止介绍的箭头组合器没有办法在另一个计算中保存值,因此我们别无选择,只能引入另一个组合器。