【Note】Haskell 的 Functor

"个人关于 Haskell 的 Functor 的一些笔记。"


个人review时的笔记

总结来讲,Functor 是一个在结构上满足恒等映射、存在可以应用在容器上映射方法的一种结构。或者说,可以应用函数将结构内的值映射为其他值、但保留结构的一种数据结构。

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,不能做额外的事情[^R.1]。

由它可以推出[^R.2]:

  • fmap f . fmap g = fmap (f . g)

即 fmap 在复合函数运算符上是满足分配律的[^R.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
 
 
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

[^R.1]: R.1 http://scarletsky.github.io/2016/02/09/what-is-functor-in-haskell/#functor-%E7%AE%80%E4%BB%8B [^R.2]: R.2 https://www.schoolofhaskell.com/user/edwardk/snippets/fmap [^R.3]: R.3 https://www.epubit.com/bookDetails?id=N20794 9.2.6

【END】

Comments is loading... / 评论区正在加载中...