为什么要在函数式编程中使用应用函子?

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

应用仿函数是提供仿函数和单子之间中点的构造,因此比单子更普遍,而比函数更有用。 通常你可以通过一个函数映射一个函数。 应用仿函数允许您采用“正常”函数(采用非函数参数)来使用它来对仿函数上下文中的多个值进行操作。 作为一个必然结果,这给了你没有monad的有效编程。

在这里可以find一个很好的,自成一体的例子。 您还可以阅读Bryan O'Sullivan开发的实用parsing示例 ,该示例不需要预先知识。

应用仿函数在需要对动作进行sorting时非常有用,但不需要命名任何中间结果。 它们比单子弱,但比函数更强(它们没有显式的绑定操作符,但它们允许在函子内运行任意函数)。

他们什么时候有用? 一个常见的例子是parsing,在这里你需要运行一些按顺序读取数据结构部分的动作,然后把所有的结果粘贴在一起。 这就像一个函数组成的一般forms:

fabcd 

在那里你可以把ab等作为要运行的任意动作,而f作为函数来应用到结果中。

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

我喜欢将它们视为重载的“空白”。 或者,那个常规的Haskell函数在身份适用函子中。

请参阅“ 应用程序编程和效果 ”

康纳尔·麦克布莱德和罗斯·帕特森的“ function明珠 ”在风格上有几个很好的例子。 这也是推广风格的首要原因。 他们用“惯用语”这个词来表示“应用函数”,但除此之外,这是很容易理解的。

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

关键的见解,正如在这里和大部分介绍这个主题时所提到的,应用程序是在函子和单子之间(甚至在函数和箭头之间)。 所有单子都是应用函数,但并非所有函数都适用。

所以有时候,我们可以使用适用的组合器来处理一些我们不能使用monadic组合器的东西。 一个这样的事情是ZipList (也可以参考这个SO问题的一些细节 ),这只是一个包装清单,以便有一个不同的Applicative实例,而不是从Monad实例的列表派生的实例。 Applicative文档使用以下一行来给出ZipList适用的直观概念:

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

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

还有其他一些不是Monad的应用游戏者(见这个 SO问题),他们很容易想出来。 为Monad提供一个可选接口是很好的,但是有时Monad是低效的,复杂的,甚至是不可能的,那就是当你需要 Applicative Functors的时候。


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

一个很好的例子:适用性parsing。

看[真实世界haskell] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517

这是具有标记的parsing器代码:

 -- 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 ab = toEnum . fst . head . readHex $ [a,b] 

“解除”可以隐藏一些重复代码的底层细节。 那么你可以用更less的话来说出确切的故事。

根据我的经验,应用仿函数是伟大的,原因如下:

某些types的数据结构承认强大的组合types,但不能真正成为单子。 事实上,function反应式编程的大部分抽象都属于这一类。 虽然我们可能在技术上能够使Behavior (又名Signal )成为单子,但它通常不能有效地完成。 应用函子允许我们在不牺牲效率的情况下仍然具有强大的构图(当然,使用应用程序有时比使用monad稍微复杂一些,因为你没有太多的结构可以使用)。

在应用函子中缺乏数据依赖性,可以让您遍历一个动作,在没有可用数据的情况下查找可能产生的所有效果。 所以你可以想象一个“networking表单”应用程序,像这样使用:

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

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

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

这是不能被渲染的,因为如果没有第一个字段的响应,第二个字段的名字就不能被知道。 我很确定有一个图书馆实现了这种forms的想法 – 我已经为自己和这个项目推出了自己的几次。

应用函数的另一个好处是它们是合成的 。 更确切地说,组成函子:

 newtype Compose fgx = Compose (f (gx)) 

只要fg是适用的。 monads也不能这样说,这就造成了整个monad变压器的故事,这个故事在一些不愉快的方面很复杂。 应用程序以这种方式超级清洁,这意味着您可以通过关注小型可组合组件来构build所需types的结构。

最近, ApplicativeDo扩展已经出现在GHC中,只要你不做任何事情,它就允许你使用应用程序的符号,简化一些符号的复杂性。

我也build议看看这个

在文章的最后有一个例子

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

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