本文是我个人Review时的笔记
本文内容都基于base-4.19.0
1.0 Functor
为什么需要 Functor ?
考虑重要的遍历函数 map
:
map :: (a -> b) -> [a] -> [b]
它实际上可被认为是:
map :: (a -> b) -> List a -> List b
这只是一个示例,即把列表
[]
看作是一个名为 “List” 的容器。
即 map
本身是这样一个函数:应用一个函数到容器上,使容器里的包裹值转变为同样包裹在容器里的其他结果。
如:
toString :: Show a => a -> String
toString = show
sample :: List Int
sample = [1,2,3]
sampleToString :: List Int -> List String
sampleToString = toString sample
同样,形如 Maybe
、 Tree
之类的类型也可被认为是一种容器,因为它们总是有构造时需要的参数。我们也总是有这样的操作:应用一个函数,将容器里的某种值映射成另一种值。故而,我们抽象出了类型类 Functor
,它有一个操作 fmap
:
class Functor f where
fmap :: (a -> b) -> f a -> f b
此处的 f
为一个kind为 * -> *
的类型,即 f
为一个类型构造器,一个容器。它需要传入一个类型而后产生出该类型,如 Maybe Int
需要 Int
才能成为 Maybe Int
。这也是它命名为 f
的原因——某种意义上,它也是一个函数,不过是定义在类型之上的。
但是,并不是所有有 fmap
操作的类型属于 Functor
,因为 Functor
需要满足这个条件:
fmap id = id
即fmap
只能对值调用 f
,不能做额外的事情1。
由它可以推出2:
fmap f . fmap g = fmap (f . g)
即 fmap 在复合函数运算符上是满足分配律的3。
总结下来,函子具有以下两条性质:
fmap id = id
fmap f . fmap g = fmap (f . g)
关于这一性质的证明,请见 R.2
2.0 Data.Functor 中的一些运算符
现代的 Haskell 里,标准库中关于 Functor
还定义了一些运算符:
请注意,下面的默认定义可能会根据具体类型的实现不同而有不同的行为。下文仅作阅读。
<$
-- 默认定义
(<$) :: a -> f b -> f a
(<$) = fmap . const
-- 在 Maybe 中只有 Just 会
ghci> 'a' <$ Just 2
Just 'a'
<$
的 flipped 版本
($>) :: Functor f => f a -> b -> f b
($>) = flip (<$)
>>> Left 8675309 $> "foo"
Left 8675309
>>> Right 8675309 $> "foo"
Right "foo"
<$>
即fmap
,中缀版本。
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap
<&>
,<$>
的 flip 版本。
(<&>) :: Functor f => f a -> (a -> b) -> f b
as <&> f = f <$> as