Application 函子是一种加强的函子,在 Haskell 的 Control.Applicative 模块中定义了一个 Applicative 类型类:
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
同普通函子一样,一个类型构造器要成为 Applicative 的实例的话,它的 kind 必须是* -> *
,即接受一个参数,返回一个具体类型。要成为 Applicative 类型类的实例,还必须定义两个函数,pure
和 <*>
。从这个定义来看,似乎只要是满足了以上几个条件的类型就可以称为 applicative 函子,事实上并非如此,要成为 applicative 函子还需要满足一条最重要的定律:
pure f <*> x = fmap f x
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
我们知道函数可以作为函子,其实函数也可以作为 applicative:
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
这个可能稍微难理解一些,pure
取一个值,产生一个最小上下文,组合成一个 applicative 值,所以产生了一个忽略参数永远返回初始值(pure 的参数)的函数。至于<*>
的话,先考虑函数作为普通函子的情况,我们知道函子值是一个包涵上下文的值,当函数作为函子值时,从这个上下文中取值的操作就是将一个参数传递给该函数,然后产生一个值,所以函数作为Functor
类型类的实例时是这样的(这种情况下 fmap
其实就是函数组合.
):
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
我在函子定律中提到过,fmap
接收一个函数和一个函子值,取出函子值中的值传递给函数,然后返回一个函子值。当函数作为函子值时,fmap 还是返回一个函数(这里用 lambda 表示)。g 是函子值,我们要取出它的值,所以给它传递一个参数 x,然后将得到的值作为参数传递给 f,最后将得到的值包裹到 lambda 中(其实整个过程都是在 lambda 中,x 是 lambda 的参数)。那<*>
也同理,它接收两个函子值,返回一个函子值,当函数作为函子值时,要先分别取出 f 中的值(函数)和 g 中的值,分别将一个参数 x 传递给它们,再将 g x
作为参数传递给 f x
(由于 Haskell 自动柯里化的性质,f x
还是一个函数),最后将结果包裹到 lambda 中。
当然,将((->) r)
作为 applicative 使用不是特别重要,但大家要理解并非只有容器或者某种数据结构才能作为函子。