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

0xA*_*xAX 74 haskell functional-programming functor

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

hui*_*ker 53

Applicative functor是一种提供仿函数和monad之间中点的结构,因此比monad更广泛,但比functor更有用.通常,您可以将函数映射到仿函数上.Applicative functors允许你采用"普通"函数(采用非函数式参数)来使用它来操作函子上下文中的几个值.作为必然结果,这为您提供了无需monad的有效编程.

可以在这里找到一个充满实例的漂亮,独立的解释.您还可以阅读由Bryan O'Sullivan开发的实用解析示例,该示例不需要任何先验知识.

  • 有一些有用的链接,但我不认为它们可以取代一个专注于回答问题的简短示例,尽可能地删除所有不重要的细节. (2认同)

Don*_*art 34

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

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

f a b c d
Run Code Online (Sandbox Code Playgroud)

在哪里可以想到a,b等等作为运行的任意动作,以及f作为应用于结果的仿函数.

f <$> a <*> b <*> c <*> d
Run Code Online (Sandbox Code Playgroud)

我喜欢将它们视为超载的'空白'.或者,常规Haskell函数在身份应用程序中.

请参阅" 使用效果进行应用编程 "


Joh*_*n L 12

Conor McBride和Ross Paterson的功能珍珠在风格上有几个很好的例子.它还负责首先推广这种风格.他们使用术语"成语"作为"applicative functor",但除此之外,它是非常容易理解的.


Has*_*ant 8

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

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

因此,有时我们可以使用应用组合器来实现我们不能使用monadic组合器的东西.一个这样的事情是ZipList(也参见这个SO问题的一些细节),它只是列表的包装,以便具有从列表的Monad实例派生的实例不同的Applicative实例.Applicative文档使用以下行来提供直观的概念ZipList:

f <$> ZipList xs1 <*> ... <*> ZipList xsn = ZipList (zipWithn f xs1 ... xsn)
Run Code Online (Sandbox Code Playgroud)

正如这里所指出的,可以制作几乎适用于ZipList的古怪Monad实例.

还有其他的应用函数函数不是Monads(参见这个 SO问题),它们很容易想出来.有一个替代的Monads接口很好,但是有时制作一个Monad是低效,复杂甚至不可能的,那就是你需要 Applicative Functors的时候.


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


luq*_*qui 7

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

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

应用程序仿函数缺乏数据依赖性允许您例如遍历一个动作,寻找它可能产生的所有效果,而无需提供数据.所以你可以想象一个"网络形式"应用程序,使用如下:

userData = User <$> field "Name" <*> field "Address"
Run Code Online (Sandbox Code Playgroud)

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

userData = do
    name <- field "Name"
    address <- field $ name ++ "'s address"
    return (User name address)
Run Code Online (Sandbox Code Playgroud)

因为第二个字段的名称在没有第一个字段的响应的情况下无法知道,所以无法呈现.我很确定有一个库实现了这种形式的想法 - 我已经为自己和那个项目做了几次.

应用函子的另一个好处是它们构成.更确切地说,构图函子:

newtype Compose f g x = Compose (f (g x))
Run Code Online (Sandbox Code Playgroud)

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

最近ApplicativeDo扩展已经出现在GHC中,它允许你使用do符号与应用程序,减轻一些符号复杂性,只要你不做任何monady事情.


Nyb*_*ble 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
Run Code Online (Sandbox Code Playgroud)

使用仿函数使其更短:

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

'lifting'可以隐藏一些重复代码的底层细节.然后你可以用更少的单词来讲述准确而准确的故事.

  • 你有一个奇怪的想法"更短" - 应用版本更长7个字符! (4认同)