函子

引言

上一篇文章介绍了范畴论的一些基本概念,范畴,对象,态射。并且用了一些编程语言的例子去实践了这些理论的东西。但这还不足以让我们对函数式编程有个深刻的理解,因为还有一些概念是我们必须要知道,比如函子。

什么是函子

函子(Functor)在维基百科中说,

在范畴论中,函子是范畴间的一类映射。函子也可以解释为小范畴范畴内的态射。

我们先记住前面一句话,函子是范畴间的一类映射。我们知道,态射是范畴内对象之间的映射关系。函子与它类似,函子是范畴与范畴间的映射关系,也就是可以通过一个函子,把一个范畴映射到另一个范畴。

函子_第1张图片

上图中有两个范畴 C 和 D,还有一个函子 F,意思是范畴 C 通过函子 F 映射到了D。
我们用白话描述一下函子的定义,免得陷入各种希腊字母的公式中。
- 范畴 C 中的每一个对象,都可以映射到范畴 D 上的一个对象(这个可以多对一);
- 范畴 C 中的每一个态射 f: X->Y ,都可以遇到到范畴 D 上的相应态射 F(f) : F(X)->F(Y), 包括单位态射;
- 映射后的态射的复合规则依然被保持,即 f:X->Y, g:Y->Z, 恒有 F(g.f)=F(g).F(f)。

还有,把一个范畴映射到自身的函子叫做自函子。

实例

回到上面给出的图示中解释一下函子。
上面有个函子,把范畴 C 中的 string 映射到了范畴 D 中的 List[string],同样把 int 映射到了 D 中的 List[int]。并且态射 f 和单位态射也依然保持。
我们来看一下实际的例子:

template<class C, class D>
std::list fmap(std::function f, std::list c)
{
  std::list d;
  std::transform(std::begin(c),
      std::end(c),
      std::back_inserter(d),
      f);
  return d;
}

int main() {
  std::list<std::string> c;
  c.push_back("1");
  c.push_back("2");

  std::list<int> c1 = fmap<std::string, int>([](std::string obj) -> int {
    return atoi(obj.c_str());
  }, c);

  return 0;
}

由于语法的局限性,这段代码写的并不漂亮,但也大致能体现出函数子的意思。
我们对照定义来解析一下:
对象的映射,如 string 映射到 list 体现在由类型的转换中,在模板实例化时,其实就暗含着 string 到 list 的转换,只不过这里的 string,和 list 各是范畴 C 和范畴 D 里的对象;
那态射的映射体现在哪里呢?即态射 f: string->int 映射到哪里了呢?
这里的 f 相当于一个签名为输入类型为 C ,输出类型为 D 的函数,它应该映射到输入类型为 std::list ,输出为类型 std::list 的函数,即这里的 fmap 所做的过程。
这里的 fmap 在实例化以及运算的过程,相当于函子的原理,其实所有带有这种工作原理与 fmap 相似的结构都可以叫函子。

自函子(Endofunctor)

假设我们现在把我们的范畴定义的更大一些,定义它的对象包含了我们见过的所有类型,其它它是一个无限集,因为 std::list 这样也是类型,也相当于这个范畴的对象,并且可以无限扩展,比如 list

结束语

有时候一些习以为常的代码里暗含着一些理论基础,当理解了这些理论基础以后,会更加清晰地认识到这些代码背后的思想。

你可能感兴趣的:(Functional,Programming)