【Note】Haskell中的 Functor

59 阅读2分钟

本文是我个人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

同样,形如 MaybeTree 之类的类型也可被认为是一种容器,因为它们总是有构造时需要的参数。我们也总是有这样的操作:应用一个函数,将容器里的某种值映射成另一种值。故而,我们抽象出了类型类 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

总结下来,函子具有以下两条性质:

  1. fmap id = id
  2. fmap f . fmap g = fmap (f . g)

关于这一性质的证明,请见 R.2

2.0 Data.Functor 中的一些运算符

现代的 Haskell 里,标准库中关于 Functor 还定义了一些运算符:

请注意,下面的默认定义可能会根据具体类型的实现不同而有不同的行为。下文仅作阅读。

  1. <$
-- 默认定义
(<$) :: a -> f b -> f a
(<$) =  fmap . const

-- 在 Maybe 中只有 Just 会
ghci> 'a' <$ Just 2
Just 'a'
  1. <$ 的 flipped 版本
($>) :: Functor f => f a -> b -> f b
($>) = flip (<$)

>>> Left 8675309 $> "foo"
Left 8675309
>>> Right 8675309 $> "foo"
Right "foo"
  1. <$>fmap ,中缀版本。
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap
  1. <&><$> 的 flip 版本。
(<&>) :: Functor f => f a -> (a -> b) -> f b
as <&> f = f <$> as

Reference

Footnotes

  1. R.1 scarletsky.github.io/2016/02/09/…

  2. R.2 www.schoolofhaskell.com/user/edward…

  3. R.3 www.epubit.com/bookDetails… 9.2.6