个人review时的笔记
总结:Applicative 是扩展的函子,它可以将函数应用到函子上,并且能够将计算过程串联在一起,并且组合出计算过程的结果。
1.0 Applicative ,可应用函子
什么是 Applicative (Functor)?
Applicative 是可应用函子,对函子的扩展。可应用的函子的特性在于它能够支持这样的运算:
> Just (+) <*> Just 1 <*> Just 2
Just 3
即可应用函子能够将计算过程串联在一起,并组合它们的结果。1
Applicative的定义为:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
pure 用于将一个值提升为一个函子,或者说,将它包装到容器中。
可以把
pure看作是fmap0。以此为基础不断扩展多个函子的操作:fmap0::a -> f afmap1::(a->b) -> f a -> f bfmap2::(a->b->c) -> f a -> f b -> f c... 数量总不是问题。
<*> 则是一个中缀运算符,将一个被包装在容器里的函数应用到包装在容器里的值,而后产生结果。
关于 <*> ,横向对比 Functor 的 <$> 会发现一些很有意思的东西:
(<*>) :: f (a -> b) -> f a -> f b
(<$>) :: (a -> b) -> f a -> f b
即 <*> 传入了一个包装过的函数,而不是普通的函数。2这也是为什么它叫做 Applicative Functor,因为它是可应用函数的函子。它能够调用包装在容器内的函数。3
比较有意思的是,你可以通过运用 <$> 和 <$> 创建记录:
>>> data MyState = MyState {arg1 :: Foo, arg2 :: Bar, arg3 :: Baz}
>>> produceFoo :: Applicative f => f Foo
>>> produceBar :: Applicative f => f Bar
>>> produceBaz :: Applicative f => f Baz
>>> mkState :: Applicative f => f MyState
>>> mkState = MyState <$> produceFoo <*> produceBar <*> produceBaz
liftA2 则是提升一个二元函数后,将它应用到两个容器上。如:
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
>>> liftA2 (,) (Just 3) (Just 5)
Just (3,5)
2.0 有趣的运算符 *> 与 <*
运算符 *>与 <* 用于将计算过程串联起来。但是在计算过程中,会按序忽略第一个或第二个计算结果:
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
它可以用来构建更加简洁且有效率的计算过程,如库文档中给出的示例:
>>> import Data.Char
>>> import Text.ParserCombinators.ReadP
>>> let p = string "my name is " *> munch1 isAlpha <* eof
>>> readP_to_S p "my name is Simon"
[("Simon","")]
这段代码中,p是一个组合起来的解析器。它由以下解析器组合起来:
string "my name is ",匹配字符串是否由 "my name is" 开头munch1 isAlpha,用于匹配所有属于UnicodeLl,Lu,Lt,Lo,Lm之一的字符类的字符。4eof用于判断是否到了输入流的末尾。
这个解析器做的便是匹配形如 my name is xxxx 的字符串:
- 先匹配字符串是否是由 "my name is" 开头。
- 如果成功,此时输入流已经到了"my name is"的结尾处,而后会忽略刚才匹配的结果,将控制权转给
munch1 isAlpha,它将继续配对"my name is" 之后的所有属于UnicodeLl,Lu,Lt,Lo,Lm之一的字符类的字符。因此空格不算,因为空格属于Zs, 是 space separator。 - 当匹配到非上述类的字符时,例如空格,则会将控制权转给
eof,但是保留它的匹配结果。eof判断是否到了输入流的末尾,如果不是末尾,则抛出Fail。如果成功,那么最终返回前面保留的匹配结果。 - 由于计算是串联在一起的。因此其中的一个环节出错抛出Fail后,都会直接返回代表Fail的结果。
完成以上,只是巧妙利用了 Applicative 的特性构造出了一个 Parser let p = string "my name is " *> munch1 isAlpha <* eof 。
这便是 Applicative的妙处。
3.0 Applicative 还需要满足的性质
一个函子是可应用的,应该还要满足以下性质:
- 为单位元,
pure id <*> v = v - 是可复合的,
pure (.) <*> u <*> v <*> w = u <*> (v <*> w) - 是同态的,
pure f <*> pure x = pure (f x) - 是可互换的,
u <*> pure y = pure ($ y) <*> u