应用风格的实际用途是什么?
我是一名 Scala 程序员,现在正在学习 Haskell。很容易找到 OO 概念的实际用例和现实世界示例,例如装饰器、策略模式等。书籍和互联网上充斥着这些内容。
我开始意识到,对于函数概念来说,情况并非如此。典型的例子:应用程序。
我正在努力寻找应用程序的实际用例。到目前为止,我遇到的几乎所有教程和书籍都提供了 []
和 Maybe
的示例。看到应用程序在 FP 社区中得到的所有关注,我预计应用程序会比这更适用。
我想我理解了applicatives的概念基础(也许我是错的),并且我已经等待了我的启蒙时刻很久了。但这似乎并没有发生。在编程过程中,我从来没有遇到过这样的时刻:“尤里卡!我可以在这里使用应用程序了!” (除了 []
和 Maybe
)。
有人可以指导我如何在日常编程中使用应用程序吗?我如何开始发现模式?谢谢!
I am a Scala programmer, learning Haskell now. It's easy to find practical use cases and real world examples for OO concepts, such as decorators, strategy pattern etc. Books and interwebs are filled with it.
I came to the realization that this somehow is not the case for functional concepts. Case in point: applicatives.
I am struggling to find practical use cases for applicatives. Almost all of the tutorials and books I have come across so far provide the examples of []
and Maybe
. I expected applicatives to be more applicable than that, seeing all the attention they get in the FP community.
I think I understand the conceptual basis for applicatives (maybe I am wrong), and I have waited long for my moment of enlightenment. But it doesn't seem to be happening. Never while programming, have I had a moment when I would shout with a joy, "Eureka! I can use applicative here!" (except again, for []
and Maybe
).
Can someone please guide me how applicatives can be used in a day-to-day programming? How do I start spotting the pattern? Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
这是取自 aeson 包的示例:
Here is an example taken from the aeson package:
有一些像 ZipList 这样的 ADT 可以有应用实例,但不能有单子实例。当我理解应用程序和单子之间的区别时,这是一个非常有用的例子。由于许多应用程序也是 monad,因此如果没有像 ZipList 这样的具体示例,很容易看不出两者之间的区别。
There are some ADTs like ZipList that can have applicative instances, but not monadic instances. This was a very helpful example for me when understanding the difference between applicatives and monads. Since so many applicatives are also monads, it's easy to not see the difference between the two without a concrete example like ZipList.
我认为浏览 Hackage 上的包的来源可能是值得的,并亲眼看看如何在现有的 Haskell 代码中使用应用函子等。
I think it might be worthwhile to browse the sources of packages on Hackage, and see first-handedly how applicative functors and the like are used in existing Haskell code.
我在讨论中描述了应用函子的实际使用示例,我在下面引用它。
请注意,代码示例是我的假设语言的伪代码,它将以子类型的概念形式隐藏类型类,因此,如果您看到
apply
的方法调用,只需转换为您的类型类模型,例如Scalaz 或 Haskell 中的<*>
。阅读 交互式讨论,因为我无法将其全部复制到此处。考虑到该博客的所有者是谁,我希望该网址不会损坏。例如,我引用了下面讨论中的内容。
I described an example of practical use of the applicative functor in a discussion, which I quote below.
Note the code examples are pseudo-code for my hypothetical language which would hide the type classes in a conceptual form of subtyping, so if you see a method call for
apply
just translate into your type class model, e.g.<*>
in Scalaz or Haskell.It is useful to read that interactive discussion, because I can't copy it all here. I expect that url to not break, given who the owner of that blog is. For example, I quote from further down the discussion.
当你有一个包含多个变量的普通旧函数,并且有参数但它们被包装在某种上下文中时,应用程序就很棒了。例如,您有普通的旧连接函数
(++)
但您想将其应用于通过 I/O 获取的 2 个字符串。然后,IO
是一个应用函子这一事实就派上用场了:尽管您明确要求提供非
Maybe
示例,但对我来说这似乎是一个很好的用例,所以我举个例子。您有一个由多个变量组成的常规函数,但您不知道是否拥有所需的所有值(其中一些值可能无法计算,导致Nothing
)。因此本质上是因为您有“部分值”,您希望将函数转换为部分函数,如果其任何输入未定义,则该函数也是未定义的。然后,但这
正是您想要的。
基本思想是,您将常规函数“提升”到可以将其应用于任意数量的参数的上下文中。与基本的 Functor 相比,Applicative 的额外功能在于它可以提升任意数量的函数,而 fmap 只能提升一元函数。
Applicatives are great when you've got a plain old function of several variables, and you have the arguments but they're wrapped up in some kind of context. For instance, you have the plain old concatenate function
(++)
but you want to apply it to 2 strings which were acquired through I/O. Then the fact thatIO
is an applicative functor comes to the rescue:Even though you explicitly asked for non-
Maybe
examples, it seems like a great use case to me, so I'll give an example. You have a regular function of several variables, but you don't know if you have all the values you need (some of them may have failed to compute, yieldingNothing
). So essentially because you have "partial values", you want to turn your function into a partial function, which is undefined if any of its inputs is undefined. Thenbut
which is exactly what you want.
The basic idea is that you're "lifting" a regular function into a context where it can be applied to as many arguments as you like. The extra power of
Applicative
over just a basicFunctor
is that it can lift functions of arbitrary arity, whereasfmap
can only lift a unary function.由于许多应用程序也是单子,我觉得这个问题确实有两个方面。
当两者都可用时,为什么我要使用应用程序界面而不是一元界面?
这主要是一个风格问题。尽管 monad 具有
do
表示法的语法糖,但使用应用风格通常会导致更紧凑的代码。在此示例中,我们有一个类型
Foo
,我们想要构造该类型的随机值。使用IO
的 monad 实例,我们可以写成Applicative 变体要短得多。
当然,我们可以使用 liftM2 来获得类似的简洁性,但是应用风格比必须依赖于特定数量的提升函数更简洁。
在实践中,我发现自己使用应用程序的方式与使用无点样式的方式非常相似:当一个操作更清楚地表达为其他操作的组合时,避免命名中间值。
为什么我要使用不是 monad 的 applicative?
由于 applicative 比 monad 受到更多限制,这意味着您可以提取有关它们的更有用的静态信息。
应用解析器就是一个例子。而 Monad 解析器支持使用
(>>=) :: Monad m => 的顺序组合;妈-> (a→mb)→ m b
,应用解析器仅使用(<*>) :: Applicative f => f(a→b)→发-> f b 。这些类型使差异变得显而易见:在单子解析器中,语法可以根据输入而改变,而在应用解析器中,语法是固定的。
通过以这种方式限制接口,我们可以确定解析器是否会接受空字符串而不运行它。我们还可以确定第一个和后续集,它们可用于优化,或者正如我最近一直在使用的那样,构建支持更好的错误恢复的解析器。
Since many applicatives are also monads, I feel there's really two sides to this question.
Why would I want to use the applicative interface instead of the monadic one when both are available?
This is mostly a matter of style. Although monads have the syntactic sugar of
do
-notation, using applicative style frequently leads to more compact code.In this example, we have a type
Foo
and we want to construct random values of this type. Using the monad instance forIO
, we might writeThe applicative variant is quite a bit shorter.
Of course, we could use
liftM2
to get similar brevity, however the applicative style is neater than having to rely on arity-specific lifting functions.In practice, I mostly find myself using applicatives much in the same way like I use point-free style: To avoid naming intermediate values when an operation is more clearly expressed as a composition of other operations.
Why would I want to use an applicative that is not a monad?
Since applicatives are more restricted than monads, this means that you can extract more useful static information about them.
An example of this is applicative parsers. Whereas monadic parsers support sequential composition using
(>>=) :: Monad m => m a -> (a -> m b) -> m b
, applicative parsers only use(<*>) :: Applicative f => f (a -> b) -> f a -> f b
. The types make the difference obvious: In monadic parsers the grammar can change depending on the input, whereas in an applicative parser the grammar is fixed.By limiting the interface in this way, we can for example determine whether a parser will accept the empty string without running it. We can also determine the first and follow sets, which can be used for optimization, or, as I've been playing with recently, constructing parsers that support better error recovery.
我认为 Functor、Applicative 和 Monad 都是设计模式。
想象一下您想编写一个 Future[T] 类。即,保存要计算的值的类。
在 Java 思维方式中,您可以像
Where 'get' 块一样创建它,直到该值可用。
您可能会意识到这一点,并重写它以接受回调:
但是如果将来有两种用途会发生什么?这意味着您需要保留回调列表。另外,如果一个方法接收到一个 Future[Int] 并需要返回一个基于里面的 Int 的计算,会发生什么?或者,如果您有两个 future 并且您需要根据它们提供的值进行计算,您该怎么办?
但是如果您了解 FP 概念,您就会知道您可以操纵 Future 实例,而不是直接在 T 上工作。
现在您的应用程序发生了变化,因此每次您需要处理包含的值时,您只需返回一个新的 Future。
一旦你开始走这条路,你就不能停在那里。您意识到,为了操纵两个 future,您只需要建模为应用程序,为了创建 future,您需要为 future 进行 monad 定义,等等。
更新:正如 @Eric 所建议的,我写了一篇博文:<一href="http://www.tikalk.com/incubator/blog/function-programming-scala-rest-us">http://www.tikalk.com/incubator/blog/function-programming-scala-rest-我们
I think of Functor, Applicative and Monad as design patterns.
Imagine you want to write a Future[T] class. That is, a class that holds values that are to be calculated.
In a Java mindset, you might create it like
Where 'get' blocks until the value is available.
You might realize this, and rewrite it to take a callback:
But then what happens if there are two uses for the future? It means you need to keep a list of callbacks. Also, what happens if a method receives a Future[Int] and needs to return a calculation based on the Int inside? Or what do you do if you have two futures and you need to calculate something based on the values they will provide?
But if you know of FP concepts, you know that instead of working directly on T, you can manipulate the Future instance.
Now your application changes so that each time you need to work on the contained value, you just return a new Future.
Once you start in this path, you can't stop there. You realize that in order to manipulate two futures, you just need to model as an applicative, in order to create futures, you need a monad definition for future, etc.
UPDATE: As suggested by @Eric, I've written a blog post: http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us
我终于明白了应用程序如何通过该演示文稿帮助进行日常编程:
https://web.archive.org/web/20100818221025/http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html
作者展示了应用程序如何帮助组合验证和处理失败。
演示文稿是用 Scala 编写的,但作者还提供了 Haskell、Java 和 C# 的完整代码示例。
I finally understood how applicatives can help in day-to-day programming with that presentation:
https://web.archive.org/web/20100818221025/http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html
The autor shows how applicatives can help for combining validations and handling failures.
The presentation is in Scala, but the author also provides the full code example for Haskell, Java and C#.
警告:我的回答相当说教/抱歉。所以起诉我吧。
那么,在你的日常 Haskell 编程中你多久创建一次新的数据类型?听起来您想知道何时创建自己的 Applicative 实例,老实说,除非您正在滚动自己的解析器,否则您可能不需要做太多事情。另一方面,您应该学会经常使用应用实例。
应用性不是像装饰器或策略那样的“设计模式”。它是一种抽象,这使得它更加普遍和普遍有用,但不太有形。您很难找到“实际用途”的原因是因为它的示例用途几乎太简单了。您可以使用装饰器在窗口上放置滚动条。您可以使用策略来统一国际象棋机器人的进攻性和防御性动作的界面。但应用程序有什么用呢?嗯,它们更加通用,所以很难说它们的用途,但这没关系。应用程序作为解析组合器很方便; Yesod Web 框架使用 Applicative 来帮助设置和从表单中提取信息。如果你仔细观察,你会发现 Applicative 有一百万零一个用途;到处都是。但由于它是如此抽象,您只需要感受它即可认识到它可以帮助您的生活变得更轻松的许多地方。
Warning: my answer is rather preachy/apologetic. So sue me.
Well, how often in your day-to-day Haskell programming do you create new data types? Sounds like you want to know when to make your own Applicative instance, and in all honesty unless you are rolling your own parser, you probably won't need to do it very much. Using applicative instances, on the other hand, you should learn to do frequently.
Applicative is not a "design pattern" like decorators or strategies. It is an abstraction, which makes it much more pervasive and generally useful, but much less tangible. The reason you have a hard time finding "practical uses" is because the example uses for it are almost too simple. You use decorators to put scrollbars on windows. You use strategies to unify the interface for both aggressive and defensive moves for your chess bot. But what are applicatives for? Well, they're a lot more generalized, so it's hard to say what they are for, and that's OK. Applicatives are handy as parsing combinators; the Yesod web framework uses Applicative to help set up and extract information from forms. If you look, you'll find a million and one uses for Applicative; it's all over the place. But since it's so abstract, you just need to get the feel for it in order to recognize the many places where it can help make your life easier.
我认为 Applicatives 简化了单子代码的一般使用。有多少次您遇到过这样的情况:您想要应用一个函数,但该函数不是单子函数,而您想要应用它的值是单子函数?对我来说:很多次!
这是我昨天刚刚写的一个例子:
与使用 Applicative 相比:
这种形式看起来“更自然”(至少在我看来:)
I think Applicatives ease the general usage of monadic code. How many times have you had the situation that you wanted to apply a function but the function was not monadic and the value you want to apply it to is monadic? For me: quite a lot of times!
Here is an example that I just wrote yesterday:
in comparison to this using Applicative:
This form looks "more natural" (at least to my eyes :)
来自“函子”的应用性它概括了“fmap”以轻松表达对多个参数(liftA2)或一系列参数(使用<*>)的作用。
来自“Monad”的 Applicative 它不会让计算依赖于计算的值。具体来说,您不能对返回值进行模式匹配和分支,通常您所能做的就是将其传递给另一个构造函数或函数。
因此,我认为 Applicative 夹在 Functor 和 Monad 之间。识别何时不对一元计算的值进行分支是查看何时切换到应用程序的一种方法。
Coming at Applicative from "Functor" it generalizes "fmap" to easily express acting on several arguments (liftA2) or a sequence of arguments (using <*>).
Coming at Applicative from "Monad" it does not let the computation depend on the value that is computed. Specifically you cannot pattern match and branch on a returned value, typically all you can do is pass it to another constructor or function.
Thus I see Applicative as sandwiched in between Functor and Monad. Recognizing when you are not branching on the values from a monadic computation is one way to see when to switch to Applicative.