我们已经熟悉使用map函数对列表进行操作了:
1 | map :: (a -> b) -> [a] -> [b] |
而函子就是对这一操作进行的抽象。Haskell通过类型类提供一些抽象的接口,将一些与类型相关的操作封装在了类型(对于类型类)的实现上。
函子
将map函数的功能进行抽象,发现map是将具有某种结构的数据应用一个函数,类似的我们可以定义对于树等其他数据结构的map。我们把具有这种性质的类型称为函子(Functor),通常作为数据容器的类型都可以实现函子。
函子类型类在Haskell中如此定义:
1 | class Functor f where |
实现一个函子类型实现fmap函数。对比fmap和map的类型签名,可以发现fmap就是map函数的抽象。
列表也实现了函子类型类,对列表来说,fmap就是map:
1 | instance Functor [] where |
对于Maybe类型:
1 | instance Functor Maybe where |
另外,还提供了一些运算符:
1 | (<$>) :: Functor f => (a -> b) -> f a -> f b |
其中<\$>就是fmap的中缀版本,<\$少见,是fmap.const:
1 | > "1" <$ [1,2,3] |
Functor定律
并不是只要实现了fmap的类型就是函子,函子的fmap函数应当满足一些性质保证使用时的正确性:
$$
fmap\ id=id
$$
$$
fmap\ (f\circ g)= fmap\ f\circ fmap\ g
$$
第一条定律说明fmap应该保持该类型的结构,除了应用函数外不应有其他的操作。第二条说明fmap对于函数复合运算应满足分配律。
可应用函子
当函子中的是函数时,显然是无法直接应用的,此时就需要实现可应用函子(Applicative Functor)。
1 | class Functor => Applicative f where |
pure函数将一个a类型的值变为一个可应用函子类型f a的值,而<*>函数可以将函子里的函数应用于函子里的值。
与函数应用操作符\$对比,<*>是在对应的函子类型范围应用函数,而<\$>则是直接将函数应用于函子。
1 | ($) :: (a -> b) -> a -> b |
对于Maybe类型,可以这么实现Applicative:
1 | instance Applicative Maybe where |
Nothing是<*>运算的零元;而对于Just f,则取出函数f,使用fmap函数将它应用于函子。
1 | > Just (+ 1) <*> Just 1 |
而在Control.Applicative提供了函数将a -> b的函数抬升为f a -> f b
1 | liftA :: Applicative f => (a -> b) -> f a -> f b |
liftA用于一元函数(和fmap没啥区别?),liftA2用于二元函数。
另外还有两个运算符:
1 | (*>) :: f a -> f b -> f b |
与<\$类似,<*返回前一项,而*>返回后一项
1 | > Nothing <* Just 1 |
列表也实现了Applicative,效果类似于list comprehension
1 | instance Applicative [] where |
1 | > [(+ 1),(* 2)] <*> [1,2,3] |
- 本文作者: Frankenstein
- 本文链接: https://salty-frankenstein.github.io/blog/2020/08/21/【函数式】一、函子/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!