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

问题描述 投票:63回答:11

我是Scala程序员,现在正在学习Haskell。很容易找到OO概念的实际用例和现实世界的例子,例如装饰器,策略模式等。书籍和互联网都充满了它。

我开始意识到这在某种程度上不是功能概念的情况。例证:申请人。

我正在努力寻找应用程序的实际用例。到目前为止,我遇到的几乎所有教程和书籍都提供了[]Maybe的示例。我希望应用程序比这更适用,看到他们在FP社区中得到的所有关注。

我认为我理解申请人的概念基础(也许我错了),而且我已经等待了很长一段时间的启蒙。但它似乎并没有发生。从来没有在编程的时候,我有一个时刻,我会高兴地喊,“尤里卡!我可以在这里使用应用程序!” (再次,除了[]Maybe)。

有人可以指导我如何在日常编程中使用应用程序吗?如何开始发现模式?谢谢!

scala haskell f# functional-programming applicative
11个回答
8
投票

警告:我的答案很有说服力/道歉。所以起诉我。

那么,您在日常Haskell编程中多久创建一种新的数据类型?听起来你想知道何时制作自己的Applicative实例,并且说实话,除非你自己编译解析器,否则你可能不需要这么做。另一方面,使用应用实例,您应该学会经常做。

Applicative不是像装饰者或策略那样的“设计模式”。它是一种抽象,它使它更普遍,通常更有用,但更不明显。您很难找到“实际用途”的原因是因为它的示例用途几乎太简单了。您使用装饰器将滚动条放在窗口上。您可以使用策略来统一界面,以便为您的国际象棋机器人进行攻击性和防御性移动。但是什么是适用于什么?嗯,它们更加普遍,所以很难说它们的用途是什么,这没关系。应用程序可以方便地解析组合器; Yesod Web框架使用Applicative来帮助设置和提取表单中的信息。如果你看,你会发现一百万,一个用于Applicative;它到处都是。但是因为它是如此抽象,你只需要感受它,以便识别它可以帮助你的生活更轻松的许多地方。


2
投票

我认为在Hackage上浏览软件包的来源可能是值得的,并且在现有的Haskell代码中第一手看到应用程序函数如何使用。


1
投票

我在讨论中描述了一个实际使用applicative functor的例子,我在下面引用它。

请注意,代码示例是我的假设语言的伪代码,它会以概念形式隐藏类型类,因此如果您看到data Coord = Coord { x :: Double, y :: Double } instance FromJSON Coord where parseJSON (Object v) = Coord <$> v .: "x" <*> v .: "y" 的方法调用只是转换为您的类型类模型,例如在Scalaz或Haskell的apply

如果我们用<*>null标记数组或散列映射的元素以指示它们的索引或键是有效的但是没有值,那么none在没有任何样板文件的情况下跳过无值元素,同时将操作应用于具有值的元素。更重要的是,它可以自动处理任何先验未知的Applicative语义,即WrappedT的操作(任何超过任何级别的组合,例如Hashmap[Wrapped[T]],因为applicative是可组合的,但monad不是)。

我已经可以想象它将如何使我的代码更容易理解。我可以专注于语义,而不是所有的东西让我在那里,我的语义将在Wrapped的扩展下打开,而你的所有示例代码都没有。

值得注意的是,在此之前我忘了指出你之前的例子没有模仿Hashmap[Wrapped[Wrapped2[T]]]的返回值,Applicative将是List,而不是NullableOptionMaybe。因此,即使我试图修复你的例子也没有模仿Applicative.apply

请记住,functionToApplyApplicative.apply的输入,因此容器保持控制。

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

等价。

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

而我提出的语法糖,编译器会将其转化为上述内容。

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

阅读that interactive discussion很有用,因为我不能在这里复制它。鉴于该博客的所有者是谁,我希望该网址不会中断。例如,我在讨论中进一步引用。

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

Applicative.apply用于将函数的部分应用推广到类型参数的任何嵌套(组合)级别的参数化类型(a.k.a.泛型)。这就是使更广泛的构图成为可能。通过将其拉出功能的完成评估(即返回值)之外不能实现通用性,类似于洋葱不能从内向外剥离。

因此,它不是混淆,它是一种新的自由度,目前还没有。根据我们的讨论线程,这就是为什么你必须抛出异常或将它们存储在全局变量中,因为你的语言没有这种自由度。这不是这些类别理论仿函数的唯一应用(在我在主持人队列中的评论中阐述)。

我提供了一个链接到an example抽象验证Scala,F#和C#,它目前停留在主持人队列中。比较令人讨厌的C#版本的代码。原因是因为C#没有概括。我直观地期望随着程序的增长,C#特定于案例的样板将在几何上爆炸。


64
投票

当你有一个几个变量的普通旧函数时,应用程序是很好的,你有参数,但它们被包含在某种上下文中。例如,你有普通的旧连接函数(++),但你想将它应用于通过I / O获得的2个字符串。然后,IO是一个应用函子的事实来救援:

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

即使您明确要求非Maybe示例,它对我来说似乎是一个很好的用例,所以我将举一个例子。你有几个变量的常规函数​​,但你不知道你是否拥有所需的所有值(其中一些可能无法计算,产生Nothing)。所以主要是因为你有“部分值”,你想把你的函数变成一个部分函数,​​如果它的任何输入是未定义的,那么它是未定义的。然后

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

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

这正是你想要的。

基本思想是你将一个常规函数“提升”到一个上下文中,它可以应用于你想要的任意数量的参数。 Applicative超过基本的Functor的额外力量是它可以解除任意arity的功能,而fmap只能提升一元功能。


47
投票

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

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

这主要是风格问题。虽然monad具有do-notation的语法糖,但使用applicative样式经常会导致更紧凑的代码。

在这个例子中,我们有一个类型Foo,我们想要构造这种类型的随机值。使用qadxswpoi的monad实例,我们可能会写

IO

应用变体相当短。

data Foo = Foo Int Double

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

当然,我们可以使用randomFoo = Foo <$> randomIO <*> randomIO 来获得类似的简洁,但是应用风格比不依赖于特定于arity的提升功能更整洁。

在实践中,我发现自己使用的应用程序与使用无点样式的方式大致相同:当操作更清楚地表示为其他操作的组合时,避免命名中间值。

为什么我要使用不是monad的应用程序?

由于应用程序比monad更受限制,这意味着您可以提取更多有用的静态信息。

一个例子是应用解析器。尽管monadic解析器使用liftM2支持顺序组合,但是应用解析器仅使用(>>=) :: Monad m => m a -> (a -> m b) -> m b。这些类型明显不同:在monadic解析器中,语法可以根据输入而改变,而在应用解析器中,语法是固定的。

通过以这种方式限制接口,我们可以例如确定解析器是否将接受空字符串而不运行它。我们还可以确定可以用于优化的第一组和跟随集,或者,正如我最近一直在玩的那样,构建支持更好的错误恢复的解析器。


16
投票

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

想象一下,你想写一个Future [T]课程。也就是说,一个包含要计算的值的类。

在Java思维模式中,您可以创建它

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

哪里'获得'阻止,直到价值可用。

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

trait Future[T] {
  def get: T
}

但是如果未来有两种用途会发生什么呢?这意味着你需要保留一个回调列表。此外,如果方法接收到Future [Int]并且需要返回基于Int inside的计算,会发生什么?或者,如果你有两个期货,你需要根据它们提供的价值计算某些东西,你会怎么做?

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

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

现在您的应用程序发生了变化,因此每次您需要处理所包含的值时,您只需返回一个新的Future。

一旦你开始走这条路,你就不能就此止步。你意识到,为了操纵两个期货,你只需要建模作为一个应用,为了创造未来,你需要一个monad定义未来,等等。

更新:正如@Eric所建议的那样,我写了一篇博文:trait Future[T] { def map[U](f: T => U): Future[U] }


13
投票

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

http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us

autor显示了applicative如何帮助组合验证和处理失败。

演示文稿在Scala中,但作者还提供了Haskell,Java和C#的完整代码示例。


9
投票

我认为Applicatives可以简化monadic代码的一般用法。有多少次你想要应用一个函数的情况,但函数不是monadic,你想要应用它的值是monadic?对我来说:很多次! 这是我昨天写的一个例子:

http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html

与此相比使用Applicative:

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

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


5
投票

来自“Functor”的Applicative,它概括了“fmap”,以便轻松表达对多个参数(liftA2)或一系列参数(使用<*>)的行为。

来自“Monad”的Applicative,它不会让计算依赖于计算的值。具体来说,您不能对返回的值进行模式匹配和分支,通常您可以做的就是将其传递给另一个构造函数或函数。

因此,我认为Applicative夹在Functor和Monad之间。识别何时不从monadic计算中分支值是一种查看何时切换到Applicative的方法。


4
投票

有一些像ZipList这样的ADT可以有应用实例,但不是monadic实例。在理解应用程序和monad之间的区别时,这对我来说是一个非常有用的例子。由于许多应用程序也是monad,如果没有像ZipList这样的具体示例,很容易看不到两者之间的区别。


4
投票

以下是从aeson包中获取的示例:

ghci> import Control.Applicative
ghci> toGregorian . utctDay <$> getCurrentTime
© www.soinside.com 2019 - 2024. All rights reserved.