为什么我应该在函数式编程中使用applicative functor?

时间:2011-07-04 11:21:40

标签: haskell functional-programming functor

我是Haskell的新手,我正在阅读有关仿函数和应用函子的内容。好的,我理解仿函数以及如何使用它们,但我不明白为什么 applicative 仿函数是有用的以及我如何在Haskell中使用它们。你能用一个简单的例子向我解释为什么我需要应用仿函数吗?

7 个答案:

答案 0 :(得分:51)

Applicative functors是一种构造,它提供了仿函数和monads之间的中点,因此比monad更广泛,但比仿函数更有用。通常,您可以将函数映射到仿函数上。 Applicative functors允许你采用“普通”函数(采用非函数式参数)来使用它来操作函子上下文中的几个值。作为推论,这可以让你在没有monad的情况下进行有效的编程。

可以找到一个充满实例的漂亮,自足的解释here。您还可以阅读由Bryan O'Sullivan开发的a practical parsing example,这不需要任何先验知识。

答案 1 :(得分:33)

当您需要对操作进行排序时,应用仿函数非常有用,但不需要命名任何中间结果。因此它们比monad弱,但比functor强(它们没有显式绑定操作符,但它们允许在functor中运行任意函数)。

什么时候有用?一个常见的例子是解析,您需要运行许多按顺序读取数据结构部分的操作,然后将所有结果粘合在一起。这就像功能组合的一般形式:

f a b c d

您可以将ab等视为要执行的任意操作,并将f作为应用于结果的仿函数。

f <$> a <*> b <*> c <*> d

我喜欢将它们视为重载'空白'。或者,常规的Haskell函数位于身份应用函数中。

请参阅“Applicative Programming with Effects

答案 2 :(得分:12)

康纳麦克布莱德和罗斯帕特森的Functional Pearl风格有几个很好的例子。它还负责首先推广这种风格。他们使用术语“成语”作为“applicative functor”,但除此之外,它是非常容易理解的。

答案 3 :(得分:8)

很难想出你需要应用仿函数的例子。我可以理解为什么一个中级Haskell程序员会问他们自己这个问题,因为大多数介绍性文本只使用Applicative Functors作为方便的界面来呈现从Monads派生的实例。

在这里和大多数介绍中都提到的关键见解是,Applicative Functors位于Functors和Monads之间(甚至在Functors和Arrows之间)。所有Monads都是应用函数,但不是所有的Functors都是适用的。

因此,有时我们可以使用应用组合器来实现我们不能使用monadic组合器的东西。其中一个问题是ZipList(另请参阅this SO question for some details),它只是列表的包装,以便具有不同的Applicative实例,而不是从Monad实例派生的实例。名单。 Applicative文档使用以下行来直观地了解ZipList的用途:

f <$> ZipList xs1 <*> ... <*> ZipList xsn = ZipList (zipWithn f xs1 ... xsn)

正如here指出的那样,可以制作几乎适用于ZipList的古怪Monad实例。

还有其他一些不是Monads的应用函数(参见this SO问题),它们很容易想出来。为Monad提供另一种接口很好,但是有时制作Monad是低效,复杂甚至不可能的,那就是当你需要 Applicative Functors时。


免责声明:制作应用函数也可能效率低下,复杂且不可能,如有疑问,请咨询当地的类别理论家,以正确使用Applicative Functors。

答案 4 :(得分:7)

根据我的经验,Applicative functors很好,原因如下:

某些类型的数据结构允许强大的组合类型,但不能真正成为monad。实际上,功能反应式编程中的大部分抽象都属于这一类。虽然我们可以在技术上能够制造例如Behavior(又名Signal)monad,通常无法有效完成。应用函子允许我们仍然拥有强大的组合而不牺牲效率(诚然,有时使用应用程序而不是monad有点麻烦,因为你没有那么多的结构可以使用)。

应用程序仿函数缺乏数据依赖性允许您例如遍历一个动作,寻找在没有数据可用的情况下可能产生的所有效果。所以你可以想象一个&#34; web形式&#34;应用,像这样使用:

userData = User <$> field "Name" <*> field "Address"

并且您可以编写一个引擎来遍历查找所有使用的字段并在表单中显示它们,然后当您获得数据时再次运行它以获取构造的User。这不能用简单的仿函数(因为它将两个表单合并为一个),也不能用monad来完成,因为你可以用monad来表达:

userData = do
    name <- field "Name"
    address <- field $ name ++ "'s address"
    return (User name address)

无法呈现,因为如果没有第一个字段的响应,则无法知道第二个字段的名称。我非常确定有一个库可以实现这种形式的想法 - 我已经为自己和那个项目推出了几次。

关于applicative functors的另一个好处是它们撰写。更准确地说,组合函子:

newtype Compose f g x = Compose (f (g x))
只要fg

就适用。对于monad来说也是如此,它创造了整个monad变换器故事,这些故事以一些不愉快的方式复杂化。应用程序以这种方式非常干净,这意味着您可以通过专注于小型可组合组件来构建所需类型的结构。

最近,ApplicativeDo扩展程序出现在GHC中,允许您对应用程序使用do符号,从而减轻一些符号复杂性,只要您不做任何单一的事情。

答案 5 :(得分:6)

一个很好的例子:应用解析。

参见[real world haskell] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517

这是带有do-notation的解析器代码:

-- file: ch16/FormApp.hs
p_hex :: CharParser () Char
p_hex = do
  char '%'
  a <- hexDigit
  b <- hexDigit
  let ((d, _):_) = readHex [a,b]
  return . toEnum $ d

使用仿函数使其更短

-- file: ch16/FormApp.hs
a_hex = hexify <$> (char '%' *> hexDigit) <*> hexDigit
    where hexify a b = toEnum . fst . head . readHex $ [a,b]

'lifting'可以隐藏一些重复代码的底层细节。然后你可以用更少的单词告诉确切的&amp;精确的故事。

答案 6 :(得分:3)

我还建议您查看this

在文章的最后有一个例子

import Control.Applicative
hasCommentA blogComments =
BlogComment <$> lookup "title" blogComments
            <*> lookup "user" blogComments
            <*> lookup "comment" blogComments

这说明了应用编程风格的几个特征。