ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

伴随函子:范畴论中最优美的配对,从自由构造到遗忘函子

伴随函子:范畴论中最优美的配对,从自由构造到遗忘函子 1. 从别扭的对应说起为什么需要一个叫伴随的概念连续追了几期函子乐园的朋友可能已经发现函子这个概念的本质就是按规矩搬家——把范畴里的对象搬到另一个范畴把箭头也搬过去同时保证复合和恒等箭头的对应不被搞乱。函子解决的问题是如何映射但现实中我们经常遇到一个更别扭的情况两个范畴之间有两个方向不同的函子一个从左往右搬一个从右往左搬它们彼此不是逆映射却有着某种若即若离的默契。这个默契就是范畴论里最漂亮的构造之一——伴随函子adjoint functor。我第一次学伴随函子的时候脑子里的感觉是完全懵的。定义里写着存在自然同构 Hom_D(F c, d) ≅ Hom_C(c, G d)每个字都认识但组合在一起完全不知道在说什么。后来看了一些具体的例子才慢慢咂摸出味道来。这篇文章我想用尽量通俗的语言把我理解的伴随函子拆开揉碎讲清楚顺便聊聊它为什么是范畴论里绕不开的核心工具。先说一个生活化的类比。假设你有一个压缩工具和一个解压工具压缩工具把大文件变成小文件解压工具把小文件还原成大文件。它们不是可逆的——压缩再解压通常不等于原文件但压缩和解压之间存在一种最优匹配的关系压缩后的文件尽量小解压的时候尽量接近原文件这两个过程是天然绑定的。如果你把压缩和解压看成两个函子它们就构成一对伴随函子。压缩是左伴随它倾向于自由地丢掉信息解压是右伴随它倾向于保守地补回结构。这个类比虽然粗糙但方向是对的伴随函子描述的是一种最优接近的关系而不是精确可逆的关系。下面我们慢慢展开。2. 伴随函子的定义与直觉Hom 集的自然同构到底在说什么2.1 形式定义先给出严格定义。设有两个范畴 C 和 D有两个函子F: C → D向左伴随G: D → C向右伴随我们说 F 是 G 的左伴随G 是 F 的右伴随记作 F ⊣ G指的是对于任意对象 c ∈ Cd ∈ D存在一个关于两个变元都自然的双射Hom_D(F c, d) ≅ Hom_C(c, G d)这里的自然性指的是当 c 或 d 变化时双射要跟态射的复合相容。这个抽象条件看起来复杂但我建议你先别管自然性先理解这个对应关系的方向感。等式左边 Hom_D(F c, d) 的箭头是从 F c 出发射向 d。F c 是把 c 用 F 搬到 D 范畴里的结果。等式右边 Hom_C(c, G d) 的箭头是从 c 出发射向 G d。G d 是把 d 用 G 搬回 C 范畴里的结果。换句话说在D范畴中从F搬过去的c到d的箭头恰好一一对应C范畴中从c到G搬回来的d的箭头。2.2 一个直觉模型自由构造与遗忘函子最经典的例子是自由群和遗忘函子之间的伴随。先看遗忘函子U: Grp → Set它把群 G 映射到它的底层集合 |G|忽略掉群运算。再看自由群函子F: Set → Grp它把集合 S 映射到以 S 为生成元的自由群 F(S)。F 和 U 之间就有一个伴随关系。看看 Hom 集的对应Hom_Set(S, |G|) ≅ Hom_Grp(F(S), G)左边是集合映射从生成元集合 S 到群 G 的底层集合的普通函数。右边是群同态从自由群 F(S) 到群 G 的群同态。这个对应关系说的是**给定一个从生成元集合到群元素集的任意函数都能唯一扩展成一个从自由群到群的群同态。**这就是自由群的自由性——只要指定了生成元去哪同态就完全确定了。反过来任意一个群同态 F(S) → G限制在生成元 S 上就得到一个普通的集合函数。这个例子的直觉在于自由群函子 F 是尽量释放结构的构造——它把集合里的元素都变成独立的生成元不附加任何关系。遗忘函子 U 是尽量忽略结构的构造。它们俩就像是给结构和剥结构的两个方向天然匹配。F 是 U 的左伴随U 是 F 的右伴随。2.3 再举一个简单到极致的例子Curry 化如果你觉得自由群例子还不够亲切我们来看一个所有写过程序的人都见过的操作——Curry 化。考虑集合范畴 Set。有两个函子(-) × A: Set → Set把集合 X 映射到 X × A笛卡尔积(-)^A: Set → Set把集合 X 映射到函数集合 Hom_Set(A, X)从 A 到 X 的全体函数这两个函子之间也有伴随关系Hom_Set(X × A, Y) ≅ Hom_Set(X, Hom_Set(A, Y))这其实就是柯里化和反柯里化一个接受 (x, a) 返回 y 的二元函数可以等价地看成接受 x、返回一个从 a 到 y 的函数的一元函数。你写函数式编程的时候天天在用这个对应(X * A) - Y和X - (A - Y)可以通过curry/uncurry互相转换。这里的左伴随是 (-) × A向右加上一个参数右伴随是 (-)^A把参数变成高阶函数。这个例子把伴随函子从抽象的数学术语拉回到了日常编码中。为什么这个对应是自然的因为当你改变 X 或 Y 的时候转化的规则是同一个模板完全不依赖于具体元素内容。2.4 单位与余单位两个近似恒等的自然变换伴随函子除了 Hom 集对应之外还有另外一个等价的描述方式单位unit和余单位counit。给定伴随 F ⊣ G可以构造两个自然变换单位 η: id_C → G ∘ F从恒等函子到 GF 的自然变换余单位 ε: F ∘ G → id_D从 F G 到恒等函子的自然变换在自由群例子里η 把集合 S 映射到 S 的自由群 F(S)再把 F(S) 遗忘回集合得到的是把集合元素作为生成元嵌入的自然嵌入 S → U(F(S))。你确实可以这么做先把元素变成生成元不去管任何关系再忘掉运算结构得到的集合包含原来的集合。ε 则给出从 F(U(G))——忘掉 G 的运算再重新自由生成——到 G 的自然投射它把自由生成的词约化成群的运算。这两个自然变换满足三角恒等式triangle identities。用文字描述就是对任意对象 c复合(F ⟶ F G F ⟶ F) 先经 η 扩张再经 ε 收缩恒等于恒等自然变换。这部分细节比较多但直觉很明确η 是嵌入ε 是投影它们在适当的次序下互相抵消。如果你只想记住一句话伴随函子的本质是给一个自由构造配上它的遗忘构造两者在 Hom 集上达成一一对应在对象上用 η 和 ε 完成尽量接近的双向操作。3. 伴随函子不是孤立玩具几个关键例子拆解3.1 直积与对角函子的伴随关系范畴论里很多构造都可以用伴随函子统一起来。比如直积就是对角函子的右伴随。考虑对角函子 Δ: C → C × C它把对象 X 映射到 (X, X)把箭头 f 映射到 (f, f)。直积函子 (- × -): C × C → C把 (A, B) 映射到 A × B。伴随关系是Hom_{C × C}(Δ X, (A, B)) ≅ Hom_C(X, A × B)左边是一对箭头 (X → A, X → B)右边是一个箭头 X → A × B。这个对应正是直积的泛性质给定了两个投影就唯一确定了到直积的箭头。反过来一个到直积的箭头通过投影就可以得到两个箭头。这个例子告诉我们许多在范畴论教材里单独定义的泛性质universal property其实就是某种伴随关系的特殊形式。直积的泛性质、等化子equalizer的泛性质、自由对象的泛性质全都是伴随关系的单点案例。3.2 在函数式编程里的 Monad 视角伴随函子还有一个著名的推论如果 F ⊣ G那么复合 T G ∘ F: C → C 构成一个单子monad单位就是 η乘法 μ: T² → T 来自 ε 在中间的作用。你在 Haskell 里写过的return和本质上就是某个伴随函子产生的单子结构。比如列表单子[]把集合 A 映射到有限序列集合 [A]自由幺半群。遗忘函子把幺半群忘成集合。这背后的伴随就是前面说的自由结构-遗忘模板。理解了伴随函子你就能理解为什么 Monad 的return一定要满足那些看似繁琐的律——它们其实就是三角恒等式在单子上的投影。学到这里你会发现范畴论并不只是抽象地为了抽象而抽象。伴随函子把最优构造和遗忘结构用同一个自然同构统一起来而单子则是这个统一关系的副产品。3.3 伴随函子之间的搭档感存在唯一性问题这里有一个非常重要、也非常容易被忽视的实操性质给定一个函子它的左伴随如果存在在同构意义下是唯一的。右伴随同理。这意味着伴随关系是一种唯一配对的结构——你不可能找到两个互不同构的自由群函子都作为遗忘函子的左伴随。这个唯一性有什么用在数学研究里如果你想证明两个范畴等价经常会通过展示它们具有某种共同的伴随性质来完成。伴随关系把两个函子绑定得如此紧密以至于只要构造出一个好像是对的伴随你就已经成功了一大半。我自己的经验是看到一个函子先别急着分析它的内部结构问问自己——它有没有一个明显的左右伴随如果有那么它的很多性质比如保持极限/余极限自动就成立了。这个先找伴随的研究习惯比任何技巧都管用。4. 为什么伴随函子是现代数学的万能钥匙4.1 伴随函子保持极限与余极限这是伴随函子最实用也最迷人的定理之一左伴随保持余极限colimit右伴随保持极限limit。回头看前面的例子左伴随 F 是自由构造它要保余极限——在集合范畴里的余积不交并经过自由群函子会变成自由群的余积自由积。右伴随 U 是遗忘函子它保极限——群的直积遗忘成集合后就是集合的笛卡尔积。这在直觉上非常顺畅自由构造把加结构过程中产生的合并操作完整保留下来遗忘函子把减结构过程中限制操作完整保留下来。这条定理的实际价值在于如果你想证明某个函子是否保持极限直接去看它有没有左/右伴随。如果有结论自动成立不需要做复杂的极限图验证。在现场做研究的时候这是省时省力的利器。4.2 伴随函子与范畴等价的关系很多人容易把伴随和等价搞混。它们不是一回事但关系密切。如果一个伴随对 F ⊣ G 的单位 η 和余单位 ε 都是自然同构那么 F 和 G 就构成一对范畴等价equivalence of categories。换句话说范畴等价是伴随同构的特例。大多数时候伴随的单位和余单位不是同构——比如自由群单位只是嵌入不是同构。泛函分析里著名的Gelfand-Naimark 定理、代数几何里的仿射概形与环的对应都可以用伴随函子的视角统一理解。理解了伴随到等价的提升条件你就能在研究中判断两个范畴的对应到底只是伴随还是更进一步构成等价。4.3 伴随函子的复合如果 F₁ ⊣ G₁且 F₂ ⊣ G₂那么复合函子 F₂ ∘ F₁ 和 G₁ ∘ G₂ 也构成伴随对F₂ ∘ F₁ ⊣ G₁ ∘ G₂这个复合规则赋予伴随关系一种类似代数运算的结构。在编程语言的语义学中从简单的语法范畴到复杂语义范畴的各个层次解释器往往就是通过这种复合构造出来的。这条性质也提醒我们并不是所有函子都有伴随。有没有伴随是一个函子结构好不好的重要指标。如果一个函子没有左伴随那它可能连一些基本的余极限都保持不了这对很多构造的完整性是一个严重的警告信号。5. 如何找/验证伴随函子方法、经验与常见陷阱5.1 方法一直接构造 Hom 集双射最朴素的方法是直接定义 Hom 集的自然双射。适合对象构造比较清晰的情形。比如前面 Curry 化的例子双射就是 currying 函数本身。这种方法的难点在于验证自然性如果你构造的双射对每个对象都成立但换了对象之后规则变了那就不能称为自然同构。我个人的建议是先证明对任意给定 c 和 d映射是双射再去证自然性。双射通常由两个方向的构造性映射给出这两个映射互为逆就拿到了双射部分。自然性的验证往往只是机械的箭头追逐不算难但很容易因为粗心漏掉某个方向的复合。5.2 方法二借助泛性质构造如果对象是通过泛性质定义的比如自由对象、极限、余极限那么伴随函子的存在性往往直接来自泛性质。例如自由群函子的存在性就是因为集合 S 生成的自由群可以看作一个泛性质构造。你不需要手算 Hom 集的对应只需要证明任意群同态都能由生成元上的映射唯一确定伴随关系就自动成立。这种方法最大的好处是泛性质给出的构造天然就是自然同构的省去了一半的验证工作。在实际研究中遇到某个函子是否存在左伴随我通常先尝试构造它的泛性质对象如果构造得出来左伴随基本就稳了。5.3 方法三用Freyd 伴随函子定理做存在性判断在实操中我们经常遇到这个函子可能有伴随但我不想费劲构造的情况。这时候 Freyd 伴随函子定理可以帮上忙如果范畴满足完备性有所有小极限、局部小性且函子保持极限并满足解集条件solution set condition那么左伴随存在。这个定理听起来吓人但应用起来很快。它的意义不在于构造出伴随而在于告诉你有还是没有。数学研究里确定存在性往往比构造具体形式更重要——你可以放心地说由 Freyd 定理左伴随存在然后直接选用它而不用把注意力花在繁琐构造上。5.4 容易踩的坑把有单子当成有伴随这里必须提醒一个经常出现的误区。很多人学了伴随产生单子之后就以为任何单子都来自某个伴随。这其实是错的——虽然每个单子确实都至少有一个伴随来源Eilenberg-Moore 范畴和 Kleisli 范畴给出的两个极端构造但并非每个单子的伴随对都容易找到也并非所有伴随对产生同样平凡的 Monad。另一个坑是左伴随和右伴随的方向千万别搞反。Hom 集对应里F 出现在左边箭头的位置Hom_D(F c, d)所以叫左伴随。这个命名跟函子作用在左边还是右边无关纯粹看它在 Hom 集同构的哪一侧。我见过不少初学者把自由群函子当成右伴随就是因为搞混了这个命名逻辑——其实 F 是 U 的左伴随。5.5 动手试一试给一个小练习如果你想把概念内化我推荐做这样一个练习考虑遗忘函子 U: Ab → Set从阿贝尔群范畴到集合范畴证明它的左伴随是自由阿贝尔群函子并写出单位 η 和余单位 ε 的具体形式。做的时候注意对比自由阿贝尔群比自由群更好因为阿贝尔群的可交换关系意味着自由阿贝尔群的构造里需要商掉交换子。这个练习能帮你理解伴随函子和结构的关系——不同的遗忘函子其左伴随给出的自由对象结构也不同。做完这个练习再想一想如果遗忘函子是 U: Mon → Set幺半群范畴到集合范畴左伴随是什么答案是自由幺半群函子也就是有限序列构造 [A]。这个例子可以直接对接编程语言里的 List Monad。你会发现你天天用的列表本质上就是字符串背后的幺半群自由构造。6. 从伴随函子出发还能走向哪里如果你认真理解了伴随函子等于拿到了范畴论里一张高度连通的地图。它连接的方向不止于此伴随函子与 Kan 扩张Kan extension有深刻的联系——事实上Kan 扩张可以看作伴随关系的高阶推广伴随函子与极限理论紧密相关保持极限的性质让它在代数几何、表示论中成为基本工具伴随函子与单子的关系则直接通向编程语言的效应系统和语义模型。在函子乐园的下一站我可以接着聊聊单子Monad与余单子Comonad的直观图景、它们和伴随函子的关系以及为什么在 Haskell 里 Monad 如此重要。也可以聊聊 Kan 扩张如何把求极限和求伴随统一起来。这些都是很好玩的话题。最后分享一点我自己的研究心得伴随函子最初看起来像是范畴论里一个抽象定义但它实际上是一个非常具体的工作方法——每当你看到两个范畴之间存在某个自然的转换规则你都可以问自己这是不是一个伴随对如果是它能带给你什么信息我在实际项目里经常用它来判断一个构造是不是最自然的。如果某个函子恰好是某个遗忘函子的左伴随那它往往是最自由、最自然的构造反过来如果是右伴随那它往往是最限制、最保守的构造。这种判断不需要艰苦的计算只需要两个函子之间的 Hom 集对应是否干净利落。说实话我学伴随函子的过程并不顺利。第一次接触到真正理解中间隔了大概半年期间反复看了无数遍自由群例子。但一旦在例子里亲眼看到那个自然同构转起来一切突然就通了。如果你现在觉得伴随函子很抽象那就多动手写几个例子的 Hom 集对应尤其是自由-遗忘那一组。只要把这一组例子彻底吃透伴随函子的直觉就长在你身上了。
返回列表