经过前面一篇对函子(Functor)的铺垫,我们现在可以看看什么是自函子(Endofunctor)了,从范畴的定义看很简单:
自函子就是一个将范畴映射到自身的函子 (A functor that maps a category to itself)
这句话看起来简单,但有个问题,如何区分自函子与Identity
函子?让我们先从简单的“自函数”来看。
自函数(Endofunction)
自函数是把一个类型映射到自身类型,比如Int=>Int
, String=>String
等
注意自函数与Identity
函数的差异,Identity
函数是什么也不做,传入什么参数返回什么参数,它属于自函数的一种特例;自函数是入参和出参的类型一致,比如 (x:Int) => x * 2
或 (x:Int) => x * 3
都属于自函数:
自函子(Endofunctor)
自函子映射的结果是自身,下图是一个简单的情况:
假设这个自函子为F
,则对于 F[Int]
作用的结果仍是Int
,对于函数f: Int=>String
映射的结果 F[f]
也仍是函数f
,所以这个自函子实际是一个Identity
函子(自函子的一种特例),即对范畴中的元素和关系不做任何改变。
那怎么描述出一个非Identity
的自函子呢?在介绍范畴在程序上的用法的资料里通常都用haskell来举例,把haskell里的所有类型和函数都放到一个范畴里,取名叫Hask,那么对于这个Hask的范畴,它看上去像是这样的:
先来解释一下(画这个图的时候做了简化),A
,B
代表普通类型如String
,Int
,Boolean
等,这些(有限的)普通类型是一组类型集合,还有一组类型集合是衍生类型(即由类型构造器与类型参数组成的),这是一个无限集合(可以无限衍生下去)。这样范畴Hask
就涵盖了haskell中所有的类型。
对于范畴Hask来说,如果有一个函子F,对里面的元素映射后,其结果仍属于Hask,比如我们用List
这个函子:
List[A], List[List[A]], List[List[List[A]]]...
发现这些映射的结果也是属于Hask范畴(子集),所以这是一个自函子,实际上在Hask范畴上的所有函子都是自函子。
我们仔细观察这个Hask
范畴的结构,发现它实际是一个fractal结构,所谓fractal(分形),是个很神奇的结构,在自然界也大量存在:
如上面的这片叶子,它的每一簇分支,形状上与整体的形状是完全一样的,即局部与整体是一致的结构(并且局部可以再分解下去)
这种结构在函数式语言里也是很常用的,最典型的如List
结构,由head
和tail
两部分组合而成,而每个tail
也是一个List
结构,可以递归的分解下去。
讨论范畴的时候最重要的就是明确范畴里的对象是什么、对像间的关系(态射、箭头)又是什么。
我有一个疑问:真的可以根据对象的构造方式而不是对象本身来区分范畴吗?或者说对象的构造方式也是对象的属性?
例如这里讨论的对象都是proper type,没有讨论态射,那么从范畴C3和C4里的对象和态射来看,他们其实都是对象为proper type的范畴。
对于自函子,我是对比着自函数来理解的,今天在微博上跟九爪交流了一下,发觉我理解的有问题。不应该按照范畴的kind来给范畴分类,这篇blog稍后我会修改一下。
这篇blog已经更新。
“假设这个自函子为F,则对于 F[Int] 作用的结果仍是Int,对于函数f: Int=>String 映射的结果 F[f] 也仍是函数f,所以这个自函子实际是一个Identity函子(自函子的一种特例),即对范畴中的元素和关系不做任何改变。”
这一段写得很难以理解
理解自函子的话,可以先降一个维度,对比一个 返回自身结果的函数,传给它什么,返回的也是什么。
只不过 函子(Functor) 是面向类型编程的,F[_] 是一个函子的话,它可以把 String 映射为 F[String],
把 Int=>String 映射为 F[Int=>String] ,如果一个函子 F[_] 对 Int 映射的结果 F[Int] 也等价于 Int 那么它就是个自函子。
其实理解Hask那张图,比单纯理解那句话更简单。那张图的描述更接近我们实际使用的场景,比如Some(1).flatMap(i => Some(i + 1))。而那张图描述的是一种特殊的自函子。
恒等函子Identity functor:把范畴中的对象和态射都对应到其自身。
这里的例子里面就是
‘类型int’(对象)映射到自己,
‘函数f:int=>string’(态射)映射到自己,
这2个过程的形式表达就是F[Int]和F[f]。
这个映射过程中函子做了件看起来很无聊的事情,实现了一个自身到自身的映射,也就是恒等。