理解 Haskell 中的箭头

问题描述 投票:0回答:1

我一直在努力掌握箭头,因为它们是大多数 FRP 实现的基础。 我想我理解基本思想 - 它们与 monad 相关,但在每个绑定运算符处存储静态信息,因此您可以遍历箭头链并查看静态信息,而无需评估整个箭头。

但是当我们开始讨论第一、第二和交换时我就迷失了。 2元组与箭头有什么关系? 教程展示了元组的内容,就好像它是明显的下一步一样,但我并没有真正看到其中的联系。

就此而言,箭头语法直观上意味着什么?

haskell arrow-abstraction
1个回答
50
投票

请查看 http://www.cs.yale.edu/homes/hudak/CS429F04/AFPLectureNotes.pdf,其中解释了 Arrows 在 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函数

uncurry :: (a -> b -> c) -> ((a, b) -> c)

uncurry (+) :: Num a => (a, a) -> a

然后我们就有了箭头

(arr.uncurry) (+) :: (Num a, Arrow f) => f (a, a) a

2元组因此而产生。然后需要像

&&&
这样的一堆函数来处理这些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)
来代替。)


编辑:来自约翰·休斯的原始论文将 Monads 推广到箭头,其原因如下

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
函数需要在b的计算过程中
保存
f
类型的输入
,以便能够为
g
提供相同的输入。同样,
f
的结果必须在
g
的计算过程中保存,以便最终可以将两个结果相加并返回。到目前为止引入的箭头组合器使我们无法在另一个计算中保存值,因此我们别无选择,只能引入另一个组合器。

© www.soinside.com 2019 - 2024. All rights reserved.