箭头的目的是什么?


62

我正在通过Haskell学习函数式编程,并且尝试通过首先理解为什么需要它们来抢占概念。

我想知道功能编程语言中箭头的目标。他们解决什么问题?我检查了http://en.wikibooks.org/wiki/Haskell/Understanding_arrowshttp://www.cse.chalmers.se/~rjmh/afp-arrows.pdf。我所了解的是,它们用于描述计算图,并且它们允许更轻松的无点样式编码。

本文假定无点样式通常更易于理解和编写。这对我来说似乎很主观。在另一篇文章(http://en.wikibooks.org/wiki/Haskell/StephensArrowTutorial#Hangman:_Main_program)中,实施了子手游戏,但是我看不到箭头如何使这种实现自然而然。

我可以找到许多描述该概念的论文,但没有找到动机。

我缺少什么?

Answers:


42

我知道我来晚了,但是您在这里有两个理论上的答案,我想提供一种可行的替代方法。我是作为Haskell noob的一个相对亲戚来的,尽管如此,他最近还是在我正在进行的一个项目上通过了Arrows的主题前进。

首先,您可以高效地解决Haskell中的大多数问题,而无需接触Arrows。一些著名的Haskellers确实不喜欢也不使用它们(有关问题的更多信息,请参见此处此处此处)。因此,如果您对自己说“嘿,我不需要这些”,请了解您可能确实是正确的。

当我第一次学习箭时,最令我沮丧的是关于该主题的教程如何不可避免地达到电路的类比。如果您查看箭头代码-至少是加糖的代码-它与硬件定义语言没有什么相似之处。您的输入在右侧排列,您的输出在左侧,如果您未能正确地将它们全部连接起来,它们将无法启动。我对自己想:是吗?这是我们结束的地方吗?我们是否创建了一种完全高级的语言,使其再次由铜线和焊料组成?

据我所能确定的,正确的答案是:实际上,是的。现在,Arrows的杀手级用例是FRP(通常认为Yampa,游戏,音乐和反应系统)。FRP面临的问题与所有其他同步消息传递系统面临的问题大致相同:如何将连续的输入流连接到连续的输出流中,而又不会丢失相关信息或出现泄漏。您可以将流建模为列表-最近的几种FRP系统都使用这种方法-但是,当您有很多输入时,列表几乎变得无法管理。您需要将自己与当前隔离。

Arrows在FRP系统中所允许的是将功能组合到网络中,同时完全抽象出对那些功能所传递的基础值的任何引用。如果您不熟悉FP,一开始可能会感到困惑,而在您了解FP的含义之后却会令人不解。您直到最近才吸收了可以抽象函数的想法,以及如何理解[(*), (+), (-)]类型为type 的列表[(a -> a -> a)]。使用Arrows,您可以将抽象进一步推进一层。

这种附加的抽象能力带有其自身的危险。一方面,它可以将GHC推入极端情况,在这种情况下,它不知道如何根据您的类型假设做出决定。您必须准备好在类型级别上进行思考-这是学习种类和RankNTypes以及其他此类主题的绝佳机会。

还有许多我称之为“愚蠢的箭头特技”的示例,在此代码中,编码器到达某个箭头组合器只是因为他或她想展示一个元组的整洁技巧。(这是我对疯狂微不足道的贡献。)当您在野外遇到它时,可以随意忽略这种热狗。

注意:如上所述,我是一个相对的菜鸟。如果我在上面发布了任何误解,请随时纠正我。


2
我很高兴自己还没有接受任何东西。感谢您提供此答案。它更侧重于用户。例子很棒。主观部分已明确定义并保持平衡。我希望那些支持这个问题的人会回来看看。
西蒙·贝格

虽然箭头肯定是您链接的解决方案的错误工具,但我想我要提一下,removeAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnA它可以更简洁明了地表示为removeAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++))
cemper93

29

这是一个“软”的答案,我不确定是否有任何引用实际上以这种方式陈述了它,但这就是我想到箭头的方式:

箭头类型A b c基本上是一种函数,b -> c但是具有更多的结构,就像一元的值M a比普通的老的具有更多的结构一样a

现在,这种额外的结构恰好取决于您正在谈论的特定箭头实例。就像单子一样IO aMaybe a每个都有不同的附加结构。

单子所带来的东西是无法从一个M a变成一个a。现在,这似乎是一个限制,但实际上是一个功能:类型系统可以保护您避免将一元值转换为简单的旧值。您只能通过>>=或通过特定monad实例的原始操作参与monad来使用该值。

同样,您从中得到的A b c是无法构造新的消耗b的c生成“函数”。箭头通过参与各种箭头组合器或使用特定箭头实例的原始操作来保护您免于使用b和创建c例外。

例如,Yampa中的信号函数大致是(Time -> a) -> (Time -> b),但是它们必须服从某种因果关系限制:当时的输出t取决于输入信号的过去值:您无法展望未来。因此,他们要做的不是用进行编程(Time -> a) -> (Time -> b),而是用进行编程,SF a b然后从基元中构建信号功能。碰巧的是,由于SF a b其行为很像一个函数,因此通用结构就是所谓的“箭头”。


“箭头通过参与各种箭头组合器或使用特定箭头实例的原始操作来保护您免于使用b和创建c例外。” 对此道歉,我深表歉意:这句话使我想到了线性类型,即资源无法克隆或消失。您认为可能有任何联系吗?
glaebhoerl 2014年

14

我喜欢将Monads和Functors之类的Arrows视为允许程序员执行奇特的函数组合。

如果没有Monads或Arrows(和Functors),则功能语言中的功能组合仅限于将一个功能应用于另一个功能的结果。使用monad和函子,您可以定义两个函数,然后编写单独的可重用代码,这些代码指定在特定monad上下文中这些函数如何相互交互以及与传递给它们的数据进行交互。此代码位于Monad的绑定代码中。因此,monad是一个视图,只是一个用于可重用绑定代码的容器。函数在一个monad上下文中的构成与另一monad不同。

一个简单的示例是Maybe monad,其中bind函数中有代码,这样,如果函数A由Maybe monad中的函数B组成,而B产生Nothing,则绑定代码将确保两个函数都输出Nothing,而不必费心将A应用于从B发出的Nothing值。如果没有monad,程序员将不得不将代码写入A以测试Nothing输入。

Monads还意味着程序员无需在源代码中明确键入每个函数所需的参数-绑定函数处理参数传递。因此,使用monad,源代码可以开始看起来更像是一条静态的函数名称链,而不是看起来函数A使用参数C和D来“调用”函数B -代码开始看起来更像是电子电路,而不是移动机器-功能比命令更重要。

箭头还将功能与绑定功能连接在一起,从而提供可重复使用的功能和隐藏参数。但是Arrows本身可以连接在一起并组成,并且可以选择在运行时将数据路由到其他Arrows。现在,您可以将数据应用于“箭头”的两条路径,这对数据“做不同的事情”,然后重新组合结果。或者,您可以根据数据中的某些值选择将数据传递到哪个Arrows分支。产生的代码甚至更像是一个电子电路,带有开关,延迟,积分等。该程序看起来非常静态,并且您应该看不到正在处理的大量数据。需要考虑的参数越来越少,也不需要考虑参数可以采用或不采用哪些值。

编写Arrowized程序主要涉及选择现成的Arrows,例如分配器,开关,延迟和积分器,将功能提升到这些Arrows中,并将这些Arrows连接在一起以形成更大的Arrows。在Arrowized Functional Reactive Programming中,箭头形成了一个循环,来自世界的输入与程序最后一次迭代的输出组合在一起,因此输出对真实世界的输入做出反应。

时间是现实世界的价值之一。在Yampa中,“信号功能箭头”通过计算机程序隐式地显示了时间参数-您从不访问时间值,但是如果将积分器箭头连接到程序中,它将输出随时间积分的值,您可以将其用于传递给其他箭头。


但这听起来像是一个应用函子(一个围绕函数的包装器,在某些特定的上下文中提供了辅助函数,以便为包装类型重用已经存在的函数)。我肯定需要阅读更多信息才能理解,但也许您可以通过指出我所缺少的东西来提供帮助
-Belun

3

只是对其他答案的补充:就个人而言,这对我(从数学上)理解什么是什么以及与我所知道的其他概念有什么关系有很大帮助。

在箭头的情况下,我发现以下论文很有用-它比较了monad,适用函子(惯用语)和箭头:成语是遗忘的,箭头是细致的, Sam Lindley,Philip Wadler和Jeremy Yallop的monad是混杂的。

我也相信没有人提及此链接,它可能会为您提供有关该主题的一些想法和文献。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.