为什么应该在函数式编程中使用应用函子?
我是 Haskell 的新手,我正在阅读有关函子和应用函子的内容。好的,我了解函子以及如何使用它们,但我不明白为什么 applicative 函子有用以及如何在 Haskell 中使用它们。你能用一个简单的例子向我解释为什么我需要应用函子吗?
I'm new to Haskell, and I'm reading about functors and applicative functors. Ok, I understand functors and how I can use them, but I don't understand why applicative functors are useful and how I can use them in Haskell. Can you explain to me with a simple example why I need applicative functors?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
应用函子是一种提供函子和monad,因此比 monad 更广泛,而比函子更有用。通常,您可以将函数映射到函子上。应用函子允许您采用“普通”函数(采用非函子参数),使用它来对函子上下文中的多个值进行操作。作为推论,这可以让您在没有 monad 的情况下进行有效的编程。
可以在此处找到充满示例的精彩、独立的解释。您还可以阅读 由 Bryan O'Sullivan 开发的实用解析示例,不需要任何先验知识。
Applicative functors are a construction that provides the midpoint between functors and monads, and are therefore more widespread than monads, while more useful than functors. Normally you can just map a function over a functor. Applicative functors allow you to take a "normal" function (taking non-functorial arguments) use it to operate on several values that are in functor contexts. As a corollary, this gives you effectful programming without monads.
A nice, self-contained explanation fraught with examples can be found here. You can also read a practical parsing example developed by Bryan O'Sullivan, which requires no prior knowledge.
当您需要对操作进行排序但不需要命名任何中间结果时,应用函子非常有用。因此,它们比 monad 弱,但比函子强(它们没有显式绑定运算符,但它们确实允许在函子内运行任意函数)。
它们什么时候有用?一个常见的示例是解析,您需要运行多个操作来按顺序读取数据结构的各个部分,然后将所有结果粘合在一起。这就像函数组合的一般形式:
您可以将
a
、b
等视为要运行的任意操作,而f
> 作为应用于结果的函子。我喜欢将它们视为超载的“空白”。或者,常规 Haskell 函数位于恒等应用函子中。
请参阅“带效果的应用程序编程”
Applicative functors are useful when you need sequencing of actions, but don't need to name any intermediate results. They are thus weaker than monads, but stronger than functors (they do not have an explicit bind operator, but they do allow running arbitrary functions inside the functor).
When are they useful? A common example is parsing, where you need to run a number of actions that read parts of a data structure in order, then glue all the results together. This is like a general form of function composition:
where you can think of
a
,b
and so on as the arbitrary actions to run, andf
as the functor to apply to the result.I like to think of them as overloaded 'whitespace'. Or, that regular Haskell functions are in the identity applicative functor.
See "Applicative Programming with Effects"
Conor McBride 和 Ross Paterson 关于这种风格的功能性珍珠有几个很好的例子。它还首先负责推广这种风格。他们使用术语“习语”来表示“应用函子”,但除此之外,它是很容易理解的。
Conor McBride and Ross Paterson's Functional Pearl on the style has several good examples. It's also responsible for popularizing the style in the first place. They use the term "idiom" for "applicative functor", but other than that it's pretty understandable.
很难举出需要应用函子的例子。我可以理解为什么中级 Haskell 程序员会问自己这个问题,因为大多数介绍性文本都提供从 Monad 派生的实例,仅使用 Applicative Functors 作为方便的接口。
正如这里和该主题的大多数介绍中所提到的,关键的见解是应用函子位于函子和 Monad 之间(甚至位于函子和箭头之间)。所有 Monad 都是 Applicative Functor,但并非所有 Functor 都是 Applicative。
因此,有时我们可以使用应用组合器来做一些我们不能使用单子组合器的事情。其中之一是
ZipList
(另请参阅这个SO问题详细信息),它只是列表的包装,以便拥有一个与列表的 Monad 实例派生的不同的 Applicative 实例。应用文档使用以下行给出了ZipList
用途的直观概念:正如所指出的
Cafe/2009-April/059088.html" rel="nofollow noreferrer">这里 是其他不是 Monad 的应用函子(请参阅 这个 SO问题)而且它们很容易想出来。拥有 Monad 的替代接口固然很好,但有时制作 Monad 效率低下、复杂,甚至不可能,而这就是您需要应用函子的时候。
免责声明:制作应用函子也可能效率低下、复杂且不可能,如有疑问,请咨询您当地的范畴理论家以正确使用应用函子。
It is hard to come up with examples where you need applicative functors. I can understand why an intermediate Haskell programmer would ask them self that question since most introductory texts present instances derived from Monads using Applicative Functors only as a convenient interface.
The key insight, as mentioned both here and in most introductions to the subject, is that Applicative Functors are between Functors and Monads (even between Functors and Arrows). All Monads are Applicative Functors but not all Functors are Applicative.
So necessarily, sometimes we can use applicative combinators for something that we can't use monadic combinators for. One such thing is
ZipList
(see also this SO question for some details), which is just a wrapper around lists in order to have a different Applicative instance than the one derived from the Monad instance of list. The Applicative documentation uses the following line to give an intuitive notion of whatZipList
is for:As pointed out here, it is possible to make quirky Monad instances that almost work for ZipList.
There are other Applicative Functors that are not Monads (see this SO question) and they are easy to come up with. Having an alternative Interface for Monads is nice and all, but sometimes making a Monad is inefficient, complicated, or even impossible, and that is when you need Applicative Functors.
disclaimer: Making Applicative Functors might also be inefficient, complicated, and impossible, when in doubt, consult your local category theorist for correct usage of Applicative Functors.
根据我的经验,应用函子非常有用,原因如下:
某些类型的数据结构允许强大的组合类型,但不能真正成为单子。事实上,函数式反应式编程中的大多数抽象都属于这一类。虽然我们在技术上可能能够将例如
Behavior
(又名Signal
)作为一个monad,但通常无法有效地完成。应用函子让我们在不牺牲效率的情况下仍然拥有强大的组合(诚然,有时使用应用函子比使用单子更棘手,只是因为你没有那么多的结构可以使用)。应用函子中缺乏数据依赖性,允许您在没有可用数据的情况下遍历一个动作,寻找它可能产生的所有效果。因此,您可以想象一个“网络表单”应用程序,如下所示:
您可以编写一个引擎,该引擎将遍历以查找使用的所有字段并将它们显示在表单中,然后当您获取数据时再次运行它以获取构造了
用户
。这不能用普通函子(因为它将两种形式组合为一种)来完成,也不能用 monad 来完成,因为用 monad 你可以表达:无法渲染,因为在没有响应的情况下无法知道第二个字段的名称从一开始。我很确定有一个库可以实现这个表单的想法——我已经为这个和那个项目推出了几次自己的库。
应用函子的另一个好处是它们可以组合。 组合函子:
更准确地说,只要
f
和g
存在, 就适用。对于 monad 来说则不然,它创造了整个 monad 转换器的故事,在一些令人不快的方面变得复杂。这样,应用程序就非常干净,这意味着您可以通过专注于小型可组合组件来构建所需类型的结构。最近 GHC 中出现了
ApplicativeDo
扩展,它允许您在应用程序中使用do
表示法,只要您不执行任何 monady 操作,就可以减轻一些表示法的复杂性事物。In my experience, Applicative functors are great for the following reasons:
Certain kinds of data structures admit powerful types of compositions, but cannot really be made monads. In fact, most of the abstractions in functional reactive programming fall into this category. While we might technically be able to make e.g.
Behavior
(akaSignal
) a monad, it typically cannot be done efficiently. Applicative functors allow us to still have powerful compositions without sacrificing efficiency (admittedly, it is a bit trickier to use an applicative than a monad sometimes, just because you don't have quite as much structure to work with).The lack of data-dependence in an applicative functor allows you to e.g. traverse an action looking for all the effects it might produce without having the data available. So you could imagine a "web form" applicative, used like so:
and you could write an engine which would traverse to find all the fields used and display them in a form, then when you get the data back run it again to get the constructed
User
. This cannot be done with a plain functor (because it combines two forms into one), nor a monad, because with a monad you could express:which cannot be rendered, because the name of the second field cannot be known without already having the response from the first. I'm pretty sure there's a library that implements this forms idea -- I've rolled my own a few times for this and that project.
The other nice thing about applicative functors is that they compose. More precisely, the composition functor:
is applicative whenever
f
andg
are. The same cannot be said for monads, which has creates the whole monad transformer story which is complicated in some unpleasant ways. Applicatives are super clean this way, and it means you can build up the structure of a type you need by focusing on small composable components.Recently the
ApplicativeDo
extension has appeared in GHC, which allows you to usedo
notation with applicatives, easing some of the notational complexity, as long as you don't do any monady things.一个很好的例子:应用解析。
请参阅 [真实世界 haskell] 第 16 章 http://book.realworldhaskell.org/read/ using-parsec.html#id652517
这是带有 do-notation 的解析器代码:
使用函子使其更短:
“提升”可以隐藏某些重复代码的底层细节。那么你可以用更少的单词来告诉准确的&精确的故事。
One good example: applicative parsing.
See [real world haskell] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517
This is the parser code with do-notation:
Using functor make it much shorter:
'lifting' can hide the underlying details of some repeating code. then you can just use fewer words to tell the exact & precise story.
我还建议看看这个
文章中有一个示例
,它说明了应用程序编程风格的几个特征。
I would also suggest to take a look at this
In the end of the article there's an example
Which illustrates several features of applicative programming style.