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


78

我是Haskell的新手,正在阅读有关函子和应用函子的信息。好的,我了解函子以及如何使用它们,但是我不理解为什么应用函子有用,以及如何在Haskell中使用它们。您能通过一个简单的例子向我解释为什么我需要应用函子吗?


这只是我可以提供的链接,但是这里有一个示例,很好地描述了应用函子。
Hartmut P.

Answers:


56

应用函子是一种提供函子和单子之间的中点的构造,因此比函子更广泛,而比函子更有用。通常,您可以将函数映射到函子上。应用函子可让您采用“常规”函数(采用非功能性参数),并使用它对函子上下文中的多个值进行运算。作为必然结果,这使您无需编写monad就可以进行有效的编程。

这里可以找到一个很好的,自成体系的解释,上面充斥着例子。您也可以阅读Bryan O'Sullivan开发的实际解析示例,无需任何先验知识。


2
有有用的链接,但是我认为它们不能代替一个简短的简单示例来解决该问题,并尽可能地删除所有不重要的细节。
德米特里·扎伊采夫

34

当您需要对操作进行排序但不需要命名任何中间结果时,应用函子就很有用。因此,它们比monad弱,但比函子强(它们没有显式的bind运算符,但它们允许在函子内部运行任意函数)。

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

f a b c d

您可以在其中想到ab等等,作为要运行的任意操作,以及f应用于结果的函子。

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

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

请参见“带效果的应用程序编程


12

康纳·麦克布赖德(Conor McBride)和罗斯·帕特森(Ross Paterson)的Functional Pearl在风格上有几个很好的例子。首先,它还负责普及样式。他们将“成语”一词用于“应用函子”,但除此之外,这是完全可以理解的。


8

很难提供需要应用函子的示例。我能理解为什么Haskell中级程序员会问他们自己这个问题,因为大多数介绍性文本仅使用Applicative Functors作为便捷接口提供了从Monad派生的实例。

此处和在本主题的大多数介绍中都提到的关键见解是,应用函子在函子和Monad之间(甚至在函子和箭之间)。所有Monads都是适用的函子,但不是所有的Functor都是适用的函子。

因此,必然地,有时我们可以将应用组合器用于某些我们不能使用单子组合器的对象。这样的事情是一件事情ZipList有关更多详细信息,请参见此SO问题),它只是列表的包装,以便具有从列表的Monad实例派生的实例不同的Applicative实例。适用性文档使用以下行来直观地说明ZipList其用途:

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

正如指出的在这里,它有可能使离奇的单子情况下,对于ZipList差不多的工作。

还有其他的应用型函子不在单子(见SO问题),他们很容易拿出。为Monad提供替代接口是一件很不错的事,但是有时制作Monad效率低下,复杂甚至是不可能的,那就是当您需要应用函子时。


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


7

以我的经验,可应用函子很棒,原因如下:

某些类型的数据结构允许使用强大的合成类型,但实际上并不能成为单子。实际上,函数式反应式编程中的大多数抽象都属于此类。虽然从技术上讲,我们也许可以制作Behavior(aka Signal)单子,但通常无法有效地完成。应用函子使我们仍然可以在不牺牲效率的情况下拥有强大的合成功能(诚然,有时使用应用函式比使用monad有点难,只是因为您没有太多要使用的结构而已)。

应用函子中缺乏数据依赖关系,因此您可以遍历某个动作,以寻找它可能产生的所有效果而没有可用的数据。因此,您可以想象一个“ Web表单”应用程序,其用法如下:

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

并且您可以编写一个引擎,该引擎将遍历以查找所有使用的字段并以表格形式显示它们,然后当您取回数据时,再次运行它以获取结构User。这不能用普通的仿函数(因为它将两种形式组合成一个形式)或单子函数来完成,因为用单子函数可以表达:

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

不能呈现该字段,因为如果没有第一个字段的响应,则无法知道第二个字段的名称。我很确定有一个实现此表单想法的库-我为此项目做了几次自己的尝试。

应用函数的另一个优点是它们组成。更准确地说,合成函子:

newtype Compose f g x = Compose (f (g x))

是应用性,只要fg有。对于monads来说,情况并非如此,它创造了整个monad变形金刚的故事,这在某些令人不愉快的方面非常复杂。这样的应用程序是超级干净的,这意味着您可以通过专注于小型可组合组件来构建所需类型的结构。

最近,ApplicativeDo扩展名出现在GHC中,它使您可以将do符号与应用程序一起使用,从而减轻了一些符号的复杂性,只要您不做任何单调的事情即可。


6

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

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

这是带有do标记的解析器代码:

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

使用functor使其更短

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

“提升”可以隐藏一些重复代码的底层细节。那么您只需要使用较少的单词就可以准确地讲述故事。


4
您有一个“短得多”的怪异想法-适用版本长了7个字符!
Daniel Wagner

@Daniel Wagner:-__ ||,哦,我的..您对代码有很好的了解,我不得不承认。我实际上是指“垂直较短” :)
wuxb 2011年

好了,可以使用Control.Monad库中的常用函数来简化它的编写char '%' >> liftM (toEnum . fst . head . readHex) (replicateM 2 hexDigit)
HaskellElephant 2011年

或者,使用中的count组合器Parsec,然后切换回应用样式:toEnum . fst . head . readHex <$> (char '%' >> count 2 hexDigit)
拍拍

3

我也建议看看这个

在文章末尾有一个例子

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

它说明了应用程序编程样式的几个功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.