应用风格的实际用途是什么?

发布于 2024-11-30 07:50:53 字数 482 浏览 2 评论 0原文

我是一名 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

悲念泪 2024-12-07 07:50:54

这是取自 aeson 包的示例:

data Coord = Coord { x :: Double, y :: Double }

instance FromJSON Coord where
   parseJSON (Object v) = 
      Coord <
gt;
        v .: "x" <*>
        v .: "y"

Here is an example taken from the aeson package:

data Coord = Coord { x :: Double, y :: Double }

instance FromJSON Coord where
   parseJSON (Object v) = 
      Coord <
gt;
        v .: "x" <*>
        v .: "y"
满栀 2024-12-07 07:50:54

有一些像 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.

怎樣才叫好 2024-12-07 07:50:54

我认为浏览 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.

待天淡蓝洁白时 2024-12-07 07:50:54

我在讨论中描述了应用函子的实际使用示例,我在下面引用它。

请注意,代码示例是我的假设语言的伪代码,它将以子类型的概念形式隐藏类型类,因此,如果您看到 apply 的方法调用,只需转换为您的类型类模型,例如Scalaz 或 Haskell 中的 <*>

如果我们用 nullnone 标记数组或哈希映射的元素
表明它们的索引或键有效但没有价值,Applicative
无需任何样板即可跳过无价值的元素,同时
对具有值的元素应用操作。还有更多
重要的是它可以自动处理任何 Wrapped 语义
先验未知,即 T 上的操作
Hashmap[Wrapped[T]] (任何级别的组合,例如 Hashmap[Wrapped[Wrapped2[T]]] 因为 applicative 是可组合的,但 monad 不是)。

我已经可以想象它将如何让我的代码更容易编写
理解。我可以专注于语义,而不是所有
让我到达那里是很困难的,我的语义将在以下扩展下开放
已包装,而您的所有示例代码均未包装。

值得注意的是,我忘了指出你之前的例子
不要模拟 Applicative 的返回值,这将是
List,而不是 NullableOptionMaybe。所以即使我尝试
修复您的示例未模拟 Applicative.apply

记住functionToApply
Applicative.apply,因此容器保持控制。

<块引用>

list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) )

等价。

<块引用>

list1.apply( list2.apply( ... listN.map(functionToApply) ... ) )

以及我建议的编译器将翻译的语法糖
到上面。

funcToApply(list1, list2, ... list N)

阅读 交互式讨论,因为我无法将其全部复制到此处。考虑到该博客的所有者是谁,我希望该网址不会损坏。例如,我引用了下面讨论中的内容。

<块引用>

大多数程序员可能不希望将语句外控制流与赋值合并起来

Applicative.apply 用于将函数的部分应用推广到类型参数的任何嵌套(组合)级别的参数化类型(也称为泛型)。这一切都是为了使更通用的组合成为可能。通用性不能通过将其拉到函数的已完成评估(即返回值)之外来实现,类似于洋葱不能从内到外剥皮。

因此,它不是合并,而是一种新的自由度,目前您无法使用。根据我们的讨论线程,这就是为什么您必须抛出异常或将它们存储在全局变量中,因为您的语言没有这种自由度。这并不是这些范畴论函子的唯一应用(在我在版主队列中的评论中进行了阐述)。

我提供了示例的链接 在 Scala、F# 和 C# 中抽象验证,目前处于仲裁者队列中。比较令人讨厌的 C# 版本的代码。原因是C#没有通用化。我凭直觉预计,随着程序的增长,特定于 C# 案例的样板将呈几何级数爆炸。

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.

If we mark elements of an array or hashmap with null or none to
indicate their index or key is valid yet valueless, the Applicative
enables without any boilerplate skipping the valueless elements while
applying operations to the elements that have a value. And more
importantly it can automatically handle any Wrapped semantics that
are unknown a priori, i.e. operations on T over
Hashmap[Wrapped[T]] (any over any level of composition, e.g. Hashmap[Wrapped[Wrapped2[T]]] because applicative is composable but monad is not).

I can already picture how it will make my code easier to
understand. I can focus on the semantics, not on all the
cruft to get me there and my semantics will be open under extension of
Wrapped whereas all your example code isn’t.

Significantly, I forgot to point out before that your prior examples
do not emulate the return value of the Applicative, which will be a
List, not a Nullable, Option, or Maybe. So even my attempts to
repair your examples were not emulating Applicative.apply.

Remember the functionToApply is the input to the
Applicative.apply, so the container maintains control.

list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) )

Equivalently.

list1.apply( list2.apply( ... listN.map(functionToApply) ... ) )

And my proposed syntactical sugar which the compiler would translate
to the above.

funcToApply(list1, list2, ... list N)

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.

the conflation of out-of-statement control flow with assignment is probably not desired by most programmers

Applicative.apply is for generalizing the partial application of functions to parameterized types (a.k.a. generics) at any level of nesting (composition) of the type parameter. This is all about making more generalized composition possible. The generality can’t be accomplished by pulling it outside the completed evaluation (i.e. return value) of the function, analogous to the onion can’t be peeled from the inside-out.

Thus it isn’t conflation, it is a new degree-of-freedom that is not currently available to you. Per our discussion up thread, this is why you must throw exceptions or stored them in a global variable, because your language doesn’t have this degree-of-freedom. And that is not the only application of these category theory functors (expounded in my comment in moderator queue).

I provided a link to an example abstracting validation in Scala, F#, and C#, which is currently stuck in moderator queue. Compare the obnoxious C# version of the code. And the reason is because the C# is not generalized. I intuitively expect that C# case-specific boilerplate will explode geometrically as the program grows.

眉黛浅 2024-12-07 07:50:53

当你有一个包含多个变量的普通旧函数,并且有参数但它们被包装在某种上下文中时,应用程序就很棒了。例如,您有普通的旧连接函数 (++) 但您想将其应用于通过 I/O 获取的 2 个字符串。然后,IO 是一个应用函子这一事实就派上用场了:

Prelude Control.Applicative> (++) <
gt; getLine <*> getLine
hi
there
"hithere"

尽管您明确要求提供非Maybe 示例,但对我来说这似乎是一个很好的用例,所以我举个例子。您有一个由多个变量组成的常规函数​​,但您不知道是否拥有所需的所有值(其中一些值可能无法计算,导致Nothing)。因此本质上是因为您有“部分值”,您希望将函数转换为部分函数,​​如果其任何输入未定义,则该函数也是未定义的。然后,

Prelude Control.Applicative> (+) <
gt; Just 3 <*> Just 5
Just 8

但这

Prelude Control.Applicative> (+) <
gt; Just 3 <*> Nothing
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 that IO is an applicative functor comes to the rescue:

Prelude Control.Applicative> (++) <
gt; getLine <*> getLine
hi
there
"hithere"

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, yielding Nothing). 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. Then

Prelude Control.Applicative> (+) <
gt; Just 3 <*> Just 5
Just 8

but

Prelude Control.Applicative> (+) <
gt; Just 3 <*> Nothing
Nothing

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 basic Functor is that it can lift functions of arbitrary arity, whereas fmap can only lift a unary function.

扛起拖把扫天下 2024-12-07 07:50:53

由于许多应用程序也是单子,我觉得这个问题确实有两个方面。

当两者都可用时,为什么我要使用应用程序界面而不是一元界面?

这主要是一个风格问题。尽管 monad 具有 do 表示法的语法糖,但使用应用风格通常会导致更紧凑的代码。

在此示例中,我们有一个类型 Foo,我们想要构造该类型的随机值。使用 IO 的 monad 实例,我们可以写成

data Foo = Foo Int Double

randomFoo = do
    x <- randomIO
    y <- randomIO
    return $ Foo x y

Applicative 变体要短得多。

randomFoo = Foo <
gt; randomIO <*> randomIO

当然,我们可以使用 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 for IO, we might write

data Foo = Foo Int Double

randomFoo = do
    x <- randomIO
    y <- randomIO
    return $ Foo x y

The applicative variant is quite a bit shorter.

randomFoo = Foo <
gt; randomIO <*> randomIO

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.

月下客 2024-12-07 07:50:53

我认为 Functor、Applicative 和 Monad 都是设计模式。

想象一下您想编写一个 Future[T] 类。即,保存要计算的值的类。

在 Java 思维方式中,您可以像

trait Future[T] {
  def get: T
}

Where 'get' 块一样创建它,直到该值可用。

您可能会意识到这一点,并重写它以接受回调:

trait Future[T] {
  def foreach(f: T => Unit): Unit
}

但是如果将来有两种用途会发生什么?这意味着您需要保留回调列表。另外,如果一个方法接收到一个 Future[Int] 并需要返回一个基于里面的 Int 的计算,会发生什么?或者,如果您有两个 future 并且您需要根据它们提供的值进行计算,您该怎么办?

但是如果您了解 FP 概念,您就会知道您可以操纵 Future 实例,而不是直接在 T 上工作。

trait Future[T] {
  def map[U](f: T => U): Future[U]
}

现在您的应用程序发生了变化,因此每次您需要处理包含的值时,您只需返回一个新的 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

trait Future[T] {
  def get: T
}

Where 'get' blocks until the value is available.

You might realize this, and rewrite it to take a callback:

trait Future[T] {
  def foreach(f: T => Unit): Unit
}

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.

trait Future[T] {
  def map[U](f: T => U): Future[U]
}

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

拥醉 2024-12-07 07:50:53

我终于明白了应用程序如何通过该演示文稿帮助进行日常编程:

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

冷血 2024-12-07 07:50:53

警告:我的回答相当说教/抱歉。所以起诉我吧。

那么,在你的日常 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.

横笛休吹塞上声 2024-12-07 07:50:53

我认为 Applicatives 简化了单子代码的一般使用。有多少次您遇到过这样的情况:您想要应用一个函数,但该函数不是单子函数,而您想要应用它的值是单子函数?对我来说:很多次!
这是我昨天刚刚写的一个例子:

ghci> import Data.Time.Clock
ghci> import Data.Time.Calendar
ghci> getCurrentTime >>= return . toGregorian . utctDay

与使用 Applicative 相比:

ghci> import Control.Applicative
ghci> toGregorian . utctDay <
gt; getCurrentTime

这种形式看起来“更自然”(至少在我看来:)

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:

ghci> import Data.Time.Clock
ghci> import Data.Time.Calendar
ghci> getCurrentTime >>= return . toGregorian . utctDay

in comparison to this using Applicative:

ghci> import Control.Applicative
ghci> toGregorian . utctDay <
gt; getCurrentTime

This form looks "more natural" (at least to my eyes :)

迷爱 2024-12-07 07:50:53

来自“函子”的应用性它概括了“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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文