【问题标题】:Understanding Arrows in Haskell理解 Haskell 中的箭头
【发布时间】:2011-03-10 10:36:23
【问题描述】:

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

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

就此而言,箭头语法在直观上是什么意思?

【问题讨论】:

  • 函数式编程通常只允许你返回一个返回值,如果你想返回多个值你需要一个元组。如果你想写 avg lst = sum lst / length lst 你写“lst”三倍于函数式编程的限制。如果你想摆脱“lst”,你可以写 avg = (sum &&& length) >>> uncurry (/) where {(>>>) = flip (.); f &&& g = \a -> (f a, g a)} 和 uncurry 副词让你的函数接受 (f a, g a)。

标签: haskell arrows


【解决方案1】:

请查看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

二元组就是因为这个而产生的。那么就需要像&amp;&amp;&amp;这样的一堆函数来处理这些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|-----'
+--------+

(现在,为什么我们不需要像 &amp;&amp;&amp;&amp; 这样的东西来为具有 3 个参数的函数提供 3 元组?因为可以使用 ((a,b),c) 代替。)


编辑:从 John Hughes 的原始论文 Generalising Monads to Arrows 中,将原因描述为

4.1 箭头和对

然而,即使在单子的情况下,运算符 return&gt;&gt;= 是我们开始编写有用代码所需的全部,但对于箭头,类似的运算符 arr&gt;&gt;&gt; 是不够的。甚至我们之前看到的简单的一元加法函数

   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 = ...

我们必须按顺序组合fg。唯一可用的排序运算符是&gt;&gt;&gt;,但fg 没有正确的组合类型。实际上,add 函数需要在f 的计算中保存b 类型的输入,以便能够向g 提供相同的输入。同样,f 的结果必须在g 的计算中保存,以便最终可以将两个结果相加并返回。到目前为止介绍的箭头组合器让我们无法在另一个计算中保存一个值,因此我们别无选择,只能引入另一个组合器。

【讨论】:

  • 支持元组的原语真的是显而易见的答案吗?为什么不使用 Applicative 样式的原语呢?
  • @yairchu,当您尝试使用此类原语然后尝试根据它们实现(&gt;&gt;&gt;)first 时,问题就出现了。 IIRC,first 是一个棘手的问题。
  • @luqui:Donno,使用我的实验性 FRP-ish 框架 (github.com/yairchu/peakachu),我认为实现 first 没有任何问题。我只是不明白你为什么一开始就想要它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多