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

时间:2011-08-18 07:31:52

标签: scala haskell f# functional-programming applicative

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

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

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

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

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

11 个答案:

答案 0 :(得分: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只能提升一元函数。

答案 1 :(得分:47)

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

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

这主要是风格问题。虽然monad具有do的符号糖 - 符号,但使用适用的风格经常导致更紧凑的代码。

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

data Foo = Foo Int Double

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

应用变体相当短。

randomFoo = Foo <$> randomIO <*> randomIO

当然,我们可以使用liftM2来获得类似的简洁,但是应用风格比必须依赖特定的提升功能更整洁。

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

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

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

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

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

答案 2 :(得分:16)

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

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

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

一样创建它
trait Future[T] {
  def get: T
}

在获得值之前“获取”块。

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

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

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

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

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

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

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

更新:正如@Eric所建议的那样,我写了一篇博文:http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us

答案 3 :(得分:13)

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

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

autor显示了应用程序如何帮助组合验证和处理故障。

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

答案 4 :(得分:9)

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

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

与使用Applicative:

相比
ghci> import Control.Applicative
ghci> toGregorian . utctDay <$> getCurrentTime

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

答案 5 :(得分:8)

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

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

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

答案 6 :(得分:5)

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

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

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

答案 7 :(得分:4)

这是一个取自aeson包的例子:

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

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

答案 8 :(得分:4)

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

答案 9 :(得分:2)

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

答案 10 :(得分:1)

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

请注意,代码示例是我的假设语言的伪代码,它会以概念形式隐藏类型类,因此,如果您看到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)

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

  
    

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

  
     

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

     

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

     

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