Products and Coproducts
There is a common construction in category theory called the universal construction for defining objects in terms of their relationships. One way of doing this is to pick a pattern, a particular shape constructed from objects and morphisms, and look for all its occurrences in the category.
范畴论中有一个常见的构造,叫做泛构造(Universal Construction),它就是通过对象之间的关系来定义对象,其方式之一就是拮取一个模式——由对象与态射构成的一种特殊的形状,然后在范畴中观察它的各个方面。
Initial Object - 初始对象
把范畴想象成一张网,箭头从范畴的一端流向另一端。有序范畴中会出现这种现象,如偏序范畴。尝试将这一概念推广
We could generalize that notion of object precedence by saying that object 𝑎 is “more initial” than object 𝑏, if there is an arrow (amorphism) going from 𝑎 to 𝑏. We would then define the initial object as one that has arrows going to all other objects.
如果要说对象a比对象b更“初始”,那么必定存在一个箭头/态射是从a到b的。如果有个对象,它发出的所有箭头指向所有其它对象,那么这个对象就叫做初始对象。
对于一个给定范畴,可能无法保证初始对象一定存在,这还好说,但更麻烦的问题是,可能存在多个初始对象。考虑有序范畴,有序范畴要求任意两个对象之间最多存在一个箭头:小于或等于。基于此,给出定义:
The initial object is the object that has one and only one morphism going to any object in the category.
初始对象:这种对象有且仅有一个态射指向范畴中的任意一个对象
然而,即使这样定义初始对象也无法担它的唯一性(如果它存在),但是这个定义能担保最好的一件事是:在同构意义下,它具有唯一性。同构(Isomorphism)在范畴论中非常重要,过会儿再谈它。现在,我们只需要认定,初始对象的定义主要是为了让初始对象在同构意义下具备唯一性。
在此,给出几个与初始对象有关的例子:偏序集(通常称为 poset)的初始对象是那个最小的对象。有些偏序集没有初始对象——例如整数集。
集合范畴中,初始对象是空集。在Haskell总,空集相当于Void类型(C++中没有对应类型),而且从Void到任意类型的多态函数是唯一的,叫做absurd(absurd :: Void -> a)。这是一个态射族,由于它的存在,Void才成为类型范畴中的初始对象。
Terminal Object - 终端对象
The terminal object is the object with one and only one morphism coming to it from any object in the category.
终端对象,这种对象有且仅有一个态射来自范畴中的任意对象。
同样,终端对象在同构意义下具有唯一性。在偏序集内,如果存在着终端对象,那么它是那个最大的对象。在集合范畴中,终端对象是一个单例。我们已经讨论过单例,它们相当于 C++ 中的 void 类型,也相当于 Haskell 中的 unit 类型 ()。单例就是只有一个值的类型,在 C++ 中是隐式的,在 Haskell 中是显式的。已知,有且仅有一个纯函数,从任意类型到unit类型 unit :: a -> ()。因此,对于单例而言,终端对象的所有条件都是能够满足的。
注意,以上例子中,唯一性是非常重要的,因为有些集合(实际上是除了空集之外的所有集合)会有来自其它集合的态射。例如,有这样一个结果为bool类型的函数/谓词,yes :: a -> bool,但是bool不是一个终结对象,因为至少还存在另一个no :: a -> bool。坚持唯一性能够得到足够的精度,可以将终端对象的定义紧缩为一种类型。
Duality - 对偶
注意到初始对象和终结对象是对称的,二者之间唯一的区别的是态射的方向。事实上,对于任意范畴C,总能定义一个相反范畴 C' $C^{op}$,只需要将C中的箭头反转一下,然后重新定义一下态射的复合方式,相反的范畴能自然满足所有的范畴法则。例如,在C中,f :: a -> b,g :: b -> c,h :: a -> c = g ∘ f,那么逆向为f' :: b -> a,g' :: c -> b,h' :: c -> a = f' ∘ g'。恒等箭头保持不变(或者说反转后跟原本一样)。
对偶是范畴的一个非常重要的性质。提出的每一种构造,都有其对偶的构造;证明的每一种定义,都能免费获得一个“对偶”版。相反的范畴中的概念通常以“余”(“co”)开头,因此有了积/product和余积/coproduct,单子/monad和余单子/comonad,锥/cones和余锥/cocones等。不存在什么余余单子/cocomonad,因为反转两次之后又会变成原样。
一个范畴中的终端对象,在其相反范畴中就是初始对象。
Isomorphisms - 同构
定义相等是很难的,不管是从程序的角度来定义两个对象相等,还是从数学上定义两个东西相等。数学家也描述不了相等的意义,所以定义了很多种侧重不同视角的相等:命题相等、内涵相等、外延相等,还有拓扑类型理论中的路径相等之类。于是出现了一个弱化的概念-同构/isomorphism
直觉上,同构对象看上去是一样的,它们有着相同的形状。这意味着每个对象的每个部分与另一对象的某个部分形成一对一的映射,我们的“仪器”检测出它们是彼此的拷贝。在数学上,这意味着存在一个从a到b的映射,也存在一个从b到a的映射,这两个映射是互逆的。在范畴论中,我们用态射取代了映射。一个同构/isomorphism是一个可逆的态射/morphism;或者一对互逆的态射/morphism。
可以通过复合与恒等来理解互逆。若态射f和态射g的复合结果是恒等态射,那么g是f的逆。这体现为一下两方程,因为两态射存在两种复合形式
|
|
前面提到,初始/终结对象在同构意义下具有唯一性,意思就是两个初始/终结对象是同构的。考虑上面代码展示的范畴,复合g . f必定是个从a到a的态射。但是a是个初始,所以只能有一个从a到a的态射。由于是在一个范畴中,所以知道这个唯一的从a到a的态射就是恒等态射。因此,g . f等于恒等态射;同理 f . g也是恒等态射。这就证明了f和g是互逆的,因此两个初始对象就是同构的。
注意,上述证明中,我们使用的是从初始对象到它本身的态射的唯一性。也需要f和g也是唯一的,因为初始对象不仅在同构意义下具有唯一性,而且这个同构是唯一的。理论上,两个对象之间可能存在不止一种同构关系。这种“在同构意义下具有唯一性,且这个同构是唯一的”是所有泛构造的一个重要性质
Product - 积
还有一个泛构造,叫做积/Product。从积到每个成分,存在两个投影。在Haskell中,这两个函数被称为fst和snd,它们分别从元组/序对中获取第一成员和第二成员,fst :: (a, b) -> a和snd :: (a, b) -> b。
借助这些有限的前提,我们尝试定义集合范畴中对象和态射的模式,这种模式可以引导我们去构造两个集合a与b的积。这个模式由对象c和两个态射p和q构成,p与q将c分别连想a和b,p :: c -> a和q :: c -> b。所有符合这个模式的c都认为是候选积,这样的c可能有很多。
举个例子,从Haskell类型中选择Int Bool,让它们相乘,并将 Int Bool作为候选积。假设 Int是候选积,Int 能够被认为是 Int 与 Bool 相乘的候选积吗?是的,它能,因为它具有以下投影:p :: Int -> Int = \x -> x q :: Int -> Bool = _ -> True,它符合候选积的条件。
还有一个(Int, Int, Bool)的三元组,它也是个合法的候选积,因为存在p :: (Int, Int, Bool) -> Int = (x, _, _) -> x q :: (Int, Int, Bool) -> Bool = (_, _, b) -> b。注意到,第一个候选积太小了,它只覆盖了积的Int维度,第二个太大了,存在一个重复的Int维度。
我们还没有探索这个泛构造的其它部分:等级划分。我们希望比较这种模式的两个实例,也就是说对于候选积c与c’,想要进行一些比较,以便做出c比c’更好的结论。如果有个c’到c的态射m,虽然可以基于这个态射认为 c 比 c’ 更好,但是这样还是太弱了。我们还希望c的p和q比c’的p’和q’更好,这意味着可以通过态射m从p和q分别构造出p’和q’ p' :: c' -> a = p . m q' :: c' -> b = q . m。从某个角度看,有点像是m是一个公因子的感觉。
基于前面建立的直觉,现在看一下(Int, Bool)及其fst snd为何比之前我们给出的两个候选积更好
对于第一个候选积,m :: Int -> (Int, Bool) = x -> (x, True),这个候选积的两个投影p和q可重构为p x = fst (m x) = x q = snd (m x) = True
对于第二个候选积,m :: (Int, Int, Bool) = (x, _, b) -> (x, b)
现在给出理由,为何说(Int, Bool)比前两个候选积更好,先尝试找一个m’尝试构造出p和q
对第一个例子 fst = p . m' snd = q . m' 已知q总是返回True的,但是元组自己是可以有False值的,那么构造的这个 snd 是不对的,无法构造 snd
对第二个例子,能够在 p 或 q 运行后保留足够的信息,但是对于 fst 与 snd 而言,它们存在着多种因式化方式,因为 p 与 q 会忽略三元组的第 2 个元素,这就意味着我们的 m’ 可以在第 2 个元素的位置放任意东西,例如:m' (x, b) = (x, x, b) 或者 m' (x, b) = (x, 42, b) 等。
总之,对任何给定的类型c和投影p q,存在着唯一的一个 m 可将 c 映射成笛卡尔积 (a, b)。m :: c -> (a, b) = x -> (p x, q x)。这样就决定了笛卡尔积 (a, b) 是最好的候选积,这意味着这种泛构造对于集合范畴是有效的,它涵盖了任意两个集合的积。
现在,我们忘记集合,使用相同的泛构造来定义任意范畴中两个对象的积。这样的积并非总是存在,但是一旦它存在,它就在同构意义下具有唯一性,而且这个同构是唯一的。
A product of two objects 𝑎 and 𝑏 is the object 𝑐 equipped with two projections such that for any other object 𝑐′ equipped with two projections there is a unique morphism 𝑚 from 𝑐′to 𝑐 that factorizes those projections.
对象 a 与对象 b 的积是伴随两个投影的对象 c。对于任何其他伴随两个投影的对象 c’ 而言,存在唯一的从 c’ 到 c 的态射,这个态射可以因式化这两个投影。
一个高阶函数能够生成因子 m,这个高阶函数有时被称为因子生成器。对于本文的示例中,它是这样的函数:
|
|
Coproduct - 余积
同范畴论中每个构造一样,积有一个对偶,叫做余积。将积的范式中的箭头反转,就可以得到一个对象 c,它伴随两个入射 i 与 j——从 a 到 c 的态射与从 b 到 c 的态射。i :: a -> c j :: b -> c。等级也反转了,c比c’更好的条件是:存在从c到c’的态射m,可以“因式化”入射。i' = m . i j' = m . j
这个“最好”的对象就是,具有唯一的态射从其本身指向其它对象,这种对象就叫余积,并且如果它存在,那么它就在同构意义下具有唯一性,而且这个同构是唯一的。
A coproduct of two objects 𝑎 and 𝑏 is the object 𝑐 equipped with two injections such that for any other object 𝑐′ equipped with two injections there is a unique morphism 𝑚 from 𝑐 to 𝑐′ that factorizes those injections.
两个对象 a 与 b 的余积是对象 c,当且仅当 c 伴随着两个入射,而且任何一个其他的伴随两个入射的对象 c’,只存在唯一的从 c 到 c’ 的态射 m,并且 m 可以因式化这些入射。
在集合的范畴中,余积就是两个集合的不相交求并运算。集合 a 与集合 b 的不相交求并结果中的一个元素,要么是 a 中的元素,要么是 b 中的元素。
Haskell中内置的积是用元组实现,内置的余积是Either数据类型实现Either a b = Left a | Right b。
程序中的余积可以这样定义data Contact = PhoneNum Int | EmailAddr String / type Contact = PhoneNum Int | EmailAddr String
正如我们刚才所定义的积的因式生成器一样,我们也可以为余积定义一个。对于给定的候选余积 c 以及两个候选入射 i 与 j,为 Either 生成因式函数的的因式生成器可定义为:
|
|
Asymmetry - 非对称
集合的范畴不会随箭头的反转而出现对称性。
Notice that while the empty set has a unique morphism to any set (the absurd function),it has no morphisms coming back. The singleton set has a unique morphism coming to it from any set, but it also has out going morphisms to every set (except for the empty one).
空集可以向任意一个集合发出唯一的态射(absurd 函数),但是它没有其他集合发来的态射。单例集合拥有任意集合发来的唯一的态射,但它也能向任一集合(除了空集)发出态射。由终端对象发出的态射在拮取其他集合中的元素方面扮演了重要的角色(空集没有元素,因此没什么东西可拮取的)。
函数是建立在它的定义域(Domain)上的(在编程中,称之为全函数),它不必覆盖余定义域(Codomain,译注:可能叫陪域更正式一些)。我们已经看到了一些极端的例子(实际上,所有定义域是空集的函数都是极端的):定义域是单例的函数,意味着它只在余定义域上选择了一个元素。若定义域的尺度远小于余域的尺度,我们通常认为这样的函数是将定义域嵌入余定义域中了。例如,我们可以认为,定义域是单例的函数,它将单例嵌入到了余定义域中。我将这样的函数称为嵌入函数,但是数学家给从相反的角度进行命名:覆盖了余定义域的函数称为满射(Surjective)函数或映成(Onto)函数。
函数的非对称性也表现为,函数可以将定义域中的许多元素映射为余定义域上的一个元素,也就是说函数坍缩了。一个极端的例子是函数使整个集合坍缩为一个单例,unit 函数干的就是这事。这种坍缩只能通过函数的复合进行混成。两个坍缩函数的复合,其坍缩能力要强过二者单兵作战。数学家为非坍缩函数取了个名字:内射(Injective)或一对一(One-to-one)映射。
当然,有许多函数即不是嵌入的,也不是坍缩的。它们被称为双射(Bijection)函数,它们是完全对称的,因为它们是可逆的。在集合范畴中,同构就是双射的。