我是Haskell的新手,正在阅读有关函子和应用函子的信息。好的,我了解函子以及如何使用它们,但是我不理解为什么应用函子有用,以及如何在Haskell中使用它们。您能通过一个简单的例子向我解释为什么我需要应用函子吗?
我是Haskell的新手,正在阅读有关函子和应用函子的信息。好的,我了解函子以及如何使用它们,但是我不理解为什么应用函子有用,以及如何在Haskell中使用它们。您能通过一个简单的例子向我解释为什么我需要应用函子吗?
Answers:
当您需要对操作进行排序但不需要命名任何中间结果时,应用函子就很有用。因此,它们比monad弱,但比函子强(它们没有显式的bind运算符,但它们允许在函子内部运行任意函数)。
什么时候有用?解析是一个常见的示例,您需要运行许多操作以按顺序读取数据结构的各个部分,然后将所有结果粘合在一起。这就像函数组合的一般形式:
f a b c d
您可以在其中想到a
,b
等等,作为要运行的任意操作,以及f
应用于结果的函子。
f <$> a <*> b <*> c <*> d
我喜欢将它们视为重载的“空白”。或者,常规的Haskell函数位于身份应用函子中。
请参见“带效果的应用程序编程”
康纳·麦克布赖德(Conor McBride)和罗斯·帕特森(Ross Paterson)的Functional Pearl在风格上有几个很好的例子。首先,它还负责普及样式。他们将“成语”一词用于“应用函子”,但除此之外,这是完全可以理解的。
很难提供需要应用函子的示例。我能理解为什么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效率低下,复杂甚至是不可能的,那就是当您需要应用函子时。
免责声明:制作应用函子可能也效率低下,复杂且不可能,如果有疑问,请咨询您当地的分类理论家,以正确使用应用函子。
以我的经验,可应用函子很棒,原因如下:
某些类型的数据结构允许使用强大的合成类型,但实际上并不能成为单子。实际上,函数式反应式编程中的大多数抽象都属于此类。虽然从技术上讲,我们也许可以制作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))
是应用性,只要f
和g
有。对于monads来说,情况并非如此,它创造了整个monad变形金刚的故事,这在某些令人不愉快的方面非常复杂。这样的应用程序是超级干净的,这意味着您可以通过专注于小型可组合组件来构建所需类型的结构。
最近,ApplicativeDo
扩展名出现在GHC中,它使您可以将do
符号与应用程序一起使用,从而减轻了一些符号的复杂性,只要您不做任何单调的事情即可。
一个很好的例子:应用解析。
参见[真实世界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]
“提升”可以隐藏一些重复代码的底层细节。那么您只需要使用较少的单词就可以准确地讲述故事。
char '%' >> liftM (toEnum . fst . head . readHex) (replicateM 2 hexDigit)
。
count
组合器Parsec
,然后切换回应用样式:toEnum . fst . head . readHex <$> (char '%' >> count 2 hexDigit)
我也建议看看这个
在文章末尾有一个例子
import Control.Applicative
hasCommentA blogComments =
BlogComment <$> lookup "title" blogComments
<*> lookup "user" blogComments
<*> lookup "comment" blogComments
它说明了应用程序编程样式的几个功能。