因为 F 能够将一个范畴内的态射映射为另一个范畴内的态射,ToDouble 可以将一个范畴内的元素映射为另一个范畴内的元素,所以,我们可以把 F 与 ToDouble 的组合称作“函子”。函子体现了两个范畴间元素的抽象结构上的相似性。
相信看到这里你应该对范畴跟函子这两个概念有了一定的了解,现在让我们更进一步,看看 C# 中泛型与范畴之间的关系。
类型与范畴在之前,我们是以数值为基础来理解范畴这个概念的,那么现在我们从类型的层面来理解范畴。
泛型是我们非常熟悉的 C# 语言特性了,泛型类型与普通类型不一样,泛型类型可以接受一个类型参数,看起来就像是类型的函数。我们把接受函数作为参数的函数称为高阶函数,依此类推,我们就把接受类型作为参数的类型叫做高阶类型吧。这样,我们就可以从这个层面把 C# 的类型分为两类:普通类型(非泛型)和高阶类型(泛型)。
前面的例子中,我列出的 f,g,h 能够完成 int -> int 的转换,因为它们是 int 范畴内的态射。而 ToDouble 能够完成 int -> double 的转换,那我们就可以将他看作是普通类型范畴的态射,类似的,我们还可以定义出 ToInt32,ToString 这样的函数,它们都能完成两个普通类型之间的转换,所以也都可以看作是普通类型范畴的态射。
那么对于高阶类型(也就是泛型)范畴来说,是不是也存在态射这样的东西呢?答案是肯定的,举个例子,用 LINQ 把 List<int> 转换成 List<double> :
Func<List<int>, List<double>> ToDoubleList = x => x.Select(ToDouble).ToList();
不难发现,这里的 ToDoubleList 是 List<T> 类型范畴内的一个态射。不过你可能已经注意到了我们使用的 ToDouble 函数,它是普通类型范畴内的一个态射,我们仅仅通过一个 Select 函数就把普通类型范畴内的一个态射映射成了 List<T> 范畴内的一个态射(上面的例子中,是把 (int -> double) 转换成了 (List<int> -> List<double>)),而且 List<T> 还提供了能够把 int 类型转换成 List<int> 类型(type)的方法:new List<int>{ intValue },那么我们就可以把 List<T> 类(class)称为“函子”。事情变得有趣了起来。
自函子List<T> 还有一个构造函数可以允许我们使用另一个 List 对象创建一个新的 List 对象:new List<T>(list),这完成了 List<T> -> List<T> 转换,这看起来像是把 List<T> 范畴中的元素重新映射到了 List<T> 范畴中。有了这个构造函数的帮助,我们就可以试着使用 Select 来映射 List<T> 中的态射(比如,ToDoubleList):
// 这个映射后的 ToDoubleListAgain 仍然能够正常的工作
Func<List<int>, List<List<double>>> ToDoubleListAgain = x => x.Select(e => ToDoubleList(new List<int>(){e})).ToList();
这里的返回值类型看起来有些奇怪,我们得到了一个嵌套两层的 List,如果你熟悉 LINQ 的话,马上就会想到 SelectMany 函数——它能够把嵌套的 List 拍扁:
Func<List<TV>, List<TR>> FF<TV, TR>(Func<List<TV>, List<TR>> selector)
{
return xl => xl.SelectMany(x => selector(new List<int>() {x})).ToList();
}
var ToDoubleListAgain = FF(ToDoubleList);
ToDoubleListAgain(new List<int>{1})
这样,我们就实现了 (List<T1> -> List<T2>) -> (List<T1> -> List<T2>) 的映射,虽然功能上并没有什么卵用,但是却实现了把 List<T> 范畴中的态射映射到了 List<T> 范畴中的功能。现在看来,List<T> 类不仅是普通类型映射到 List<T> 的一个函子,它也是 List<T> 映射到 List<T> 的一个函子。这种能够把一个范畴映射到该范畴本畴上的函子也被称为“自函子”。
我们可以发现,C# 中大部分的自函子都通过 LINQ 拓展方法实现了 SelectMany 函数,其签名是:
SomeType<TR> SelectMany<TV, TR>(SomeType<TV> source, Func<TV, SomeType<TR>> selector);
List<T> 还有一个不接受任何参数的构造函数,它会创建出一个空的列表,我们可以把这个函数称作 unit,因为它的返回值在 List<T> 相关的一些二元运算中起到了单位 1 的作用。比如,concat(unit(), someList) 与 concat(someList, unit()) 得到的列表,在结构上是等价的。拥有这种性质的元素被称为“单位元”。
在函数式编程中,我们把拥有 SelectMany(也被叫做 bind),unit 函数的自函子称为“Monad”。