What are Functors?
初学 Haskell 时,总是不太理解{函子}^(functor)的概念。虽然看了很多文章,但还是感觉隔着一层迷雾。最近写了很多 TypeScript,和类型系统做了很多搏斗(笑),再回来看函子的概念,突然感觉比之前更清晰了。
很多数据结构,用{范型}^(generic)描述起来大都是 Structure<T>
的形式,例如 LinkedList<T>
、BinarySearchTree<T>
和 Optional<T>
等等。这类数据结构虽然对数据的组织各有差异,但都建构于某个类型之上,或者说「持有」某个类型的数据。在下文中,我们称之为{容器类型}^(container types)。
对容器类型来说,一个常见的操作就是对容器中的元素进行遍历,以生成新的元素。在没有这个抽象的情况下,你可能会写很多种不同的遍历。有了函子后,这种重复工作可以被抽象成一个简单的 fmap
函数,你可以把某个在原始类型上的变换,转换为在容器类型上的变换。当然,这是最直观的理解,如果你去翻 Haskell 中 Functor
的定义,你会发现其中还有一个操作符 <$
。
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
(<$) = fmap . const
它是做什么用的?回忆一下 const
的定义。
const :: a -> b -> a
const x _ = x
带入到上面的函数,可以看出,它代表了一个最常见的操作——丢掉原来容器类型里的内容,把某个有价值的变量(用 $
表示)输送(用 <
表示)到一个容器类型中,这么看 <$
这个符号还是挺传神的。
什么是{自函子}^(endofunctor)?
我相信大家都听过这句话了:「{单子}^(monad)不就是*{自函子}^(endofunctor)*范畴上的一个{幺半群}^(monoid)吗?有什么难理解的?」{自函子}^(endofunctor)的定义用一句话就能概括:它是一种特殊的函子,其变换的输入类型(定义域)和输出类型(值域)是同一个类型。
Haskell 中自带 Endo
类型,它的定义是参数和返回值都是相同类型的一元函数。
newtype Endo a = Endo { appEndo :: a -> a }
值得一提的是,endo 这个前缀来源于希腊语的 {ἔνδον}^(endon),含义是「{在…里面,内部的,被…吸收或包含的}^(within, inner, absorbing, or containing)」。
有关 fmap
和 map
命名
最开始读《Real World Haskell》的时候,看着示例代码里成片的 fmap
,我心里浮现出一个疑问:fmap
在数组类型上的实现明明和 map
相同,为什么不直接把 Functor
中的 fmap
改名为 map
呢?后来我在 Haskell 的 wiki 上看到了这个问题的解答。
Why not just do away with the current list-only
map
function, and renamefmap
tomap
instead? Well, that’s a good question. The usual argument is that someone just learning Haskell, when usingmap
incorrectly, would much rather see an error about lists than aboutFunctor
s.