Skip to main content

第 30 章:Lawvere 理论(Lawvere Theories)

原文:Bartosz Milewski, Category Theory for Programmers, Scala Edition, Chapter 30. 原书 PDF、.tex 源文件及相关图像采用 CC BY-SA 4.0;本译文按同一许可发布。

如今,如果谈函数式编程,就很难不提单子。但还有另一个平行宇宙:在那里,由于某种偶然,Eugenio Moggi 把注意力投向了 Lawvere 理论,而不是单子。让我们探索一下那个宇宙。

30.1 泛代数(Universal Algebra)

有很多方式可以在不同抽象层级上描述代数。我们试图找到一种通用语言,用来描述幺半群、群或环这类东西。在最简单的层次上,所有这些构造都定义了一些作用在集合元素上的运算,再加上一组这些运算必须满足的定律。比如,幺半群可以用一个满足结合律的二元运算来定义。它还有一个单位元素和单位律。不过稍微发挥一点想象力,就可以把单位元素变成一个零元运算,也就是一个不接收参数、返回集合中某个特殊元素的运算。

如果想谈论群,就要加入一个一元运算,它接收一个元素并返回它的逆元。与之配套的是左逆律和右逆律。环定义两个二元运算,再加上一些更多定律。如此等等。

整体图景是:一个代数由一组不同 n 值上的 n 元运算,以及一组等式恒等式定义。这些恒等式全部都是全称量化的。结合律方程必须对所有三个元素的可能组合成立,诸如此类。

顺便说一句,这会把域排除在考虑之外,原因很简单:零(加法单位元)没有乘法逆元。域的逆元律无法被全称量化。

如果用态射替换运算(函数),这个泛代数定义可以扩展到 Set 之外的范畴。我们不再选择一个集合,而是选择一个对象 a(称为泛对象)。一元运算只是 a 的自态射。那么其他元数怎么办?元数(arity)是给定运算的参数个数。二元运算(元数为 2)可以定义为从积 a x a 回到 a 的态射。一般的 n 元运算,是从 an 次幂到 a 的态射:

alpha_n :: a^n -> a

零元运算是从终对象(a 的零次幂)出发的态射。因此,为了定义任意代数,我们所需要的只是一个范畴,其对象是某个特殊对象 a 的各次幂。具体的代数被编码在这个范畴的 hom-set 中。概括来说,这就是 Lawvere 理论。

Lawvere 理论的推导会经过许多步骤,所以先给出路线图:

  1. 有限集合范畴 FinSet
  2. 它的骨架 F
  3. 它的对偶 F^op
  4. Lawvere 理论 L:范畴 Law 中的一个对象。
  5. Lawvere 范畴的模型 M:范畴 Mod(Law, Set) 中的一个对象。

30.2 Lawvere 理论(Lawvere Theories)

所有 Lawvere 理论都有共同的骨架。Lawvere 理论中的所有对象,都通过积(实际只是幂)由一个对象生成。但在一般范畴中,我们如何定义这些积?事实证明,可以通过来自一个更简单范畴的映射来定义积。实际上,这个更简单的范畴可以定义余积而不是积,然后我们用一个反变函子把它们嵌入目标范畴。反变函子会把余积变成积,把内射变成投影。

作为 Lawvere 范畴骨架的自然选择,是有限集合范畴 FinSet。它包含空集 0、单元素集合 1、二元素集合 2,依此类推。这个范畴中的所有对象都可以由单元素集合通过余积生成(把空集看作零元余积的特殊情形)。例如,二元素集合是两个单元素集合的和,2 = 1 + 1,在 Haskell 中可以表达为:

type Two = Either () ()

在 Scala 中则是:

type Two = Either[Unit, Unit]

不过,即使自然地认为空集只有一个,单元素集合却可能有许多不同版本。特别是,集合 1 + 0 不同于集合 0 + 1,也不同于 1,虽然它们全都同构。集合范畴中的余积并不严格满足结合律。我们可以构造一个把所有同构集合都识别起来的范畴,从而修正这种情况。这样的范畴称为骨架。

换句话说,任意 Lawvere 理论的骨架都是 FinSet 的骨架 F。这个范畴中的对象可以用自然数(包括零)来识别,它们对应于 FinSet 中集合的元素个数。余积扮演加法的角色。F 中的态射对应于有限集合之间的函数。例如,从 0n 有唯一态射(空集是初始对象),从 n0 没有态射(除了 0 -> 0),从 1nn 个态射(内射),从 n1 有一个态射,等等。这里,n 表示 F 中的一个对象,对应于 FinSet 中所有通过同构识别起来的 n 元集合。

利用范畴 F,可以把 Lawvere 理论形式化地定义为一个范畴 L,并配备一个特殊函子:

I_L :: F^op -> L

这个函子在对象上必须是双射,并且必须保持有限积(F^op 中的积与 F 中的余积相同):

I_L(m x n) = I_L m x I_L n

有时你会看到这个函子被描述为对象上恒等(identity-on-objects),意思是 FL 中的对象相同。因此我们会对它们使用相同名称,也就是用自然数表示它们。不过要记住,F 中的对象并不等同于集合(它们是同构集合的等价类)。

一般来说,L 中的 hom-set 比 F^op 中的 hom-set 更丰富。它们可以包含并不对应于 FinSet 中函数的态射(后者有时称为基本积运算)。Lawvere 理论中的等式定律编码在这些态射中。

关键观察是:F 中的单元素集合 1 会被映射到 L 中某个同样称为 1 的对象,而 L 中所有其他对象都会自动成为这个对象的幂。例如,F 中的二元素集合 2 是余积 1 + 1,所以它必须被映射到 L 中的积 1 x 1(或 1^2)。在这个意义上,范畴 F 的行为像是 L 的对数。

Lawvere 理论、模型与有限集合骨架之间的关系

L 的态射中,有一部分是由函子 I_LF 传递过来的。它们在 L 中扮演结构性角色。特别地,余积内射 i_k 会变成积投影 p_k。一个有用直觉是,把投影:

p_k :: 1^n -> 1

想象为某个 n 变量函数的原型:它忽略除了第 k 个变量之外的所有变量。反过来,F 中的常量态射 n -> 1 会变成 L 中的对角态射 1 -> 1^n。它们对应于变量复制。

L 中有趣的态射,是那些定义投影之外的 n 元运算的态射。正是这些态射区分了一个 Lawvere 理论与另一个 Lawvere 理论。它们是定义代数的乘法、加法、单位元素选择等等。不过,为了让 L 成为完整范畴,我们也需要复合运算 n -> m(或者等价地,1^n -> 1^m)。由于这个范畴结构简单,它们会成为更简单态射 n -> 1 的积。这是对一个语句的推广:返回积的函数就是一组函数的积;或者,正如我们之前见过的,hom-函子是连续的。

Lawvere 理论 L 基于 F^op,从中继承了那些定义积的“无趣”态射。它添加了描述 n 元运算的“有趣”态射(图中的虚线箭头)。

Lawvere 理论形成一个范畴 Law,其中态射是保持有限积并与函子 I 交换的函子。给定两个这样的理论 (L, I_L)(L', I'_{L'}),它们之间的态射是函子 F :: L -> L',满足:

F(m x n) = F m x F n
F . I_L = I'_{L'}

Lawvere 理论之间的态射封装了一种思想:把一个理论解释到另一个理论中。例如,如果忽略逆元,群乘法就可以解释为幺半群乘法。

最简单的平凡 Lawvere 范畴例子是 F^op 本身(对应于为 I_L 选择恒等函子)。这个没有运算也没有定律的 Lawvere 理论,恰好是 Law 中的初始对象。

此时如果能给出一个非平凡的 Lawvere 理论例子会很有帮助,但如果不先理解模型,解释起来会很困难。

30.3 Lawvere 理论的模型(Models of Lawvere Theories)

理解 Lawvere 理论的关键,是意识到一个这样的理论会概括许多共享同一结构的具体代数。例如,幺半群的 Lawvere 理论描述“成为幺半群”的本质。它必须对所有幺半群都有效。某个特定幺半群会成为这个理论的模型。

模型定义为从 Lawvere 理论 L 到集合范畴 Set 的函子。(Lawvere 理论也有使用其他范畴作为模型的推广,但这里我只关注 Set。)由于 L 的结构强烈依赖积,我们要求这样的函子保持有限积。因此,L 的一个模型,也称为 Lawvere 理论 L 上的代数,由下面这个函子定义:

M :: L -> Set
M(a x b) ~= M a x M b

注意,我们只要求在同构意义下保持积。这一点非常重要,因为严格保持积会排除大多数有趣理论。

模型保持积意味着,MSet 中的像是一列由集合 M1 的幂生成的集合;M1L 中对象 1 的像。我们把这个集合称为 a。(这个集合有时称为一个种类,sort;这种代数称为单种类代数。Lawvere 理论也可以推广到多种类代数。)特别地,来自 L 的二元运算会被映射为函数:

a x a -> a

和任何函子一样,L 中多个态射可能被压缩为 Set 中同一个函数。

顺便说一句,所有定律都是全称量化的等式,这意味着每个 Lawvere 理论都有一个平凡模型:一个常函子,把所有对象都映射到单元素集合,把所有态射都映射到其上的恒等函数。

L 中形如 m -> n 的一般态射会被映射为函数:

a^m -> a^n

如果有两个不同模型 MN,它们之间的自然变换是一族以 n 为索引的函数:

mu_n :: M n -> N n

或者等价地:

mu_n :: a^n -> b^n

其中 b = N1

注意,自然性条件保证了 n 元运算得到保持:

N f . mu_n = mu_1 . M f

其中 f :: n -> 1L 中的一个 n 元运算。

定义模型的函子形成一个模型范畴 Mod(L, Set),其中态射是自然变换。

考虑平凡 Lawvere 范畴 F^op 的一个模型。这样的模型完全由它在 1 上的值 M1 决定。由于 M1 可以是任意集合,这类模型与 Set 中的集合一样多。并且,Mod(F^op, Set) 中每个态射(函子 MN 之间的自然变换)都由它在 M1 上的分量唯一决定。反过来,每个函数 M1 -> N1 都会诱导两个模型 MN 之间的自然变换。因此,Mod(F^op, Set) 等价于 Set

30.4 幺半群的理论(The Theory of Monoids)

Lawvere 理论最简单的非平凡例子描述幺半群结构。它是一个单一理论,提炼了所有可能幺半群的结构;其含义是,这个理论的模型张成了整个幺半群范畴 Mon。我们已经见过一个泛构造,它说明每个幺半群都可以从某个合适的自由幺半群出发,通过识别一部分态射获得。因此,单个自由幺半群已经概括了大量幺半群。不过,自由幺半群有无限多个。幺半群的 Lawvere 理论 LMon 把它们全部组合到一个优雅构造中。

每个幺半群都必须有单位元,所以在 LMon 中必须有一个特殊态射 eta,从 01。注意,在 F 中不可能有对应态射。这样的态射会走相反方向,从 10,也就是在 FinSet 中从单元素集合到空集的函数。这样的函数不存在。

接下来考虑态射 2 -> 1,也就是 LMon(2, 1) 的成员,它必须包含所有二元运算的原型。当在 Mod(LMon, Set) 中构造模型时,这些态射会被映射为从笛卡尔积 M1 x M1M1 的函数。换句话说,它们是双参数函数。

问题是:只使用幺半群运算符,可以实现多少个双参数函数?把两个参数称为 ab。有一个函数会忽略两个参数并返回幺半群单位元。接着有两个投影,分别返回 ab。随后还有返回 abbaaabbaab 等等的函数。事实上,这样的双参数函数与由生成元 ab 构成的自由幺半群中的元素一样多。注意,LMon(2, 1) 必须包含所有这些态射,因为其中一个模型就是自由幺半群。在自由幺半群中,它们对应于彼此不同的函数。其他模型可能把 LMon(2, 1) 中多个态射压缩为一个函数,但自由幺半群不会。

如果把具有 n 个生成元的自由幺半群记作 n*,就可以把 hom-set L(2, 1)Mon(幺半群范畴)中的 hom-set Mon(1*, 2*) 识别起来。一般来说,我们选择 LMon(m, n)Mon(n*, m*)。换句话说,范畴 LMon 是自由幺半群范畴的对偶。

幺半群 Lawvere 理论的模型范畴 Mod(LMon, Set) 等价于所有幺半群构成的范畴 Mon

30.5 Lawvere 理论与单子(Lawvere Theories and Monads)

你可能还记得,代数理论可以用单子来描述,特别是用单子的代数来描述。因此,Lawvere 理论与单子之间存在联系并不令人意外。

首先看看 Lawvere 理论如何诱导一个单子。它通过遗忘函子和自由函子之间的伴随来做到这一点。遗忘函子 U 会为每个模型指派一个集合。这个集合由把 Mod(L, Set) 中的函子 M 作用到 L 中对象 1 上得到。

还可以利用 F^opLaw 中初始对象这个事实来推导 U。这意味着,对于任意 Lawvere 理论 L,都有唯一函子 F^op -> L。由于模型是从理论到集合的函子,这个函子会在模型上诱导反方向函子:

Mod(L, Set) -> Mod(F^op, Set)

但正如前面讨论过的,F^op 的模型范畴等价于 Set,因此得到遗忘函子:

U :: Mod(L, Set) -> Set

可以证明,如此定义的 U 总是有一个左伴随,也就是自由函子 F

对有限集合来说,这一点很容易看出。自由函子 F 生成自由代数。自由代数是 Mod(L, Set) 中的一个特定模型,它由一个包含 n 个生成元的有限集合生成。我们可以把 F 实现为可表示函子:

L(n, -) :: L -> Set

为了说明它确实是自由的,只需要证明它是遗忘函子的左伴随:

Mod(L(n, -), M) ~= Set(n, U(M))

先化简右侧:

Set(n, U(M)) ~= Set(n, M1) ~= (M1)^n ~= M n

这里我使用了这样一个事实:态射集合同构于指数对象;在这个例子中,它只是迭代积。这个伴随来自 Yoneda 引理:

[L, Set](L(n, -), M) ~= M n

遗忘函子和自由函子一起,在 Set 上定义了单子 T = U . F。因此,每个 Lawvere 理论都会生成一个单子。

事实证明,这个单子的代数范畴等价于模型范畴。

你可能记得,单子代数定义了对用单子形成的表达式进行求值的方式。Lawvere 理论定义了可用于生成表达式的 n 元运算。模型提供了对这些表达式求值的手段。

不过,单子与 Lawvere 理论之间的联系并不是双向无条件成立的。只有有限型单子才会导向 Lawvere 理论。有限型单子基于有限型函子。Set 上的有限型函子完全由它在有限集合上的作用决定。它在任意集合 a 上的作用可以用下面的余端求值:

F a = ∫^n a^n x (F n)

由于余端推广了余积或和,这个公式可以看成幂级数展开的推广。或者也可以使用这样的直觉:函子是一种广义容器。在这种情况下,装有 a 的有限型容器可以描述为形状与内容的和。这里,F n 是用于存储 n 个元素的形状集合,而内容是一个 n 元组,本身是 a^n 的一个元素。例如,列表(作为函子)是有限型的,每种元数都有一个形状。树在每种元数上有更多形状,等等。

首先,所有由 Lawvere 理论生成的单子都是有限型的,并且可以表达为余端:

T_L a = ∫^n a^n x L(n, 1)

反过来,给定 Set 上任意有限型单子 T,我们可以构造一个 Lawvere 理论。我们从为 T 构造 Kleisli 范畴开始。你可能还记得,在 Kleisli 范畴中,从 ab 的态射由底层范畴中的态射给出:

a -> T b

限制到有限集合时,这变成:

m -> T n

这个 Kleisli 范畴的对偶 Kl_T^op,再限制到有限集合上,就是所需的 Lawvere 理论。特别地,描述 Ln 元运算的 hom-set L(n, 1),由 hom-set Kl_T(1, n) 给出。

事实证明,我们在编程中遇到的大多数单子都是有限型的,显著例外是 continuation 单子。Lawvere 理论的概念也可以扩展到有限型运算之外。

30.6 作为余端的单子(Monads as Coends)

让我们更详细地考察这个余端公式。

T_L a = ∫^n a^n x L(n, 1)

首先,这个余端取自 F 中定义的一个 profunctor P

P n m = a^n x L(m, 1)

这个 profunctor 在第一个参数 n 上是反变的。考虑它如何提升态射。FinSet 中的一个态射是有限集合之间的映射:

f :: m -> n

这样的映射描述了从一个 n 元集合中选取 m 个元素(允许重复)。它可以被提升为 a 的幂之间的映射,也就是(注意方向):

a^n -> a^m

这种提升只是从 n 元组 (a1, a2, ... an) 中选取 m 个元素(可能带重复)。

例如,取 f_k :: 1 -> n,也就是从 n 元集合中选择第 k 个元素。它会提升为一个函数,接收 an 元组并返回第 k 个元素。

或者,取 f :: m -> 1,也就是把所有 m 个元素都映射到同一个元素的常量函数。它的提升是一个函数,接收 a 的单个元素并把它复制 m 次:

x -> (x, x, ... , x)

你可能注意到,相关 profunctor 在第二个参数上协变这一点并非立刻显然。hom-函子 L(m, 1) 实际上在 m 上是反变的。不过,我们取余端的范畴不是 L,而是 F。余端变量 n 遍历有限集合(或这些集合的骨架)。范畴 L 包含 F 的对偶,所以 F 中的态射 m -> nLL(n, m) 的成员(嵌入由函子 I_L 给出)。

来检查 L(m, 1) 作为从 FSet 的函子的函子性。我们想提升一个函数 f :: m -> n,因此目标是实现从 L(m, 1)L(n, 1) 的函数。对应于函数 fL 中有一个从 nm 的态射(注意方向)。把这个态射前组合到 L(m, 1) 中,就得到 L(n, 1) 的一个子集。

在 Lawvere 理论中通过前组合提升有限集合映射

注意,通过提升函数 1 -> n,我们可以从 L(1, 1) 到达 L(n, 1)。稍后会用到这个事实。

反变函子 a^n 与协变函子 L(m, 1) 的积,是一个 profunctor F^op x F -> Set。记住,余端可以定义为 profunctor 所有对角成员的余积(不交和),再识别其中某些元素。这些识别对应于 cowedge 条件。

这里,余端一开始是所有 n 上集合 a^n x L(n, 1) 的不交和。可以通过把余端表示为一个余等化子来生成这些识别。我们从离对角项 a^n x L(m, 1) 开始。为了到达对角线,可以把态射 f :: m -> n 作用到积的第一个分量,也可以作用到第二个分量。随后这两个结果会被识别。

a^n x L(m, 1) --<f,id>--> a^m x L(m, 1)
a^n x L(m, 1) --<id,f>--> a^n x L(n, 1)

我之前展示过,提升 f :: 1 -> n 会得到两个变换:

a^n -> a

以及:

L(1, 1) -> L(n, 1)

因此,从 a^n x L(1, 1) 出发,在提升 <f, id> 时可以到达:

a x L(1, 1)

而在提升 <id, f> 时可以到达:

a^n x L(n, 1)

不过,这并不意味着 a^n x L(n, 1) 的所有元素都可以与 a x L(1, 1) 识别起来。原因是,并非 L(n, 1) 的所有元素都能从 L(1, 1) 到达。记住,我们只能提升来自 F 的态射。L 中非平凡的 n 元运算无法通过提升态射 f :: 1 -> n 构造出来。

换句话说,在余端公式中,我们只能识别那些通过应用基本态射、能从 L(1, 1) 到达 L(n, 1) 的加项。它们全部等价于 a x L(1, 1)。基本态射就是 F 中态射的像。

来看 Lawvere 理论最简单的情形,也就是 F^op 本身。在这样的理论中,每个 L(n, 1) 都能从 L(1, 1) 到达。这是因为 L(1, 1) 是只包含恒等态射的单元素集合,而 L(n, 1) 只包含对应于 F 中内射 1 -> n 的态射,也就是基本态射。因此,余积中的所有加项都是等价的,于是得到:

T a = a x L(1, 1) = a

这就是恒等单子。

30.7 副作用的 Lawvere 理论(Lawvere Theory of Side Effects)

既然单子与 Lawvere 理论之间有这么强的联系,自然会问:Lawvere 理论能否在编程中作为单子的替代方案?单子的主要问题是它们不能很好地组合。构造单子变换器没有通用配方。Lawvere 理论在这方面有优势:它们可以用余积和张量积组合。另一方面,只有有限型单子才能轻易转换成 Lawvere 理论。这里的异常者是 continuation 单子。这个方向仍有研究在进行(见参考阅读)。

为了让你感受一下 Lawvere 理论如何用于描述副作用,我会讨论异常这个简单例子;传统上,它们使用 Maybe 单子实现。

Maybe 单子由带有单个零元运算 0 -> 1 的 Lawvere 理论生成。这个理论的模型是一个函子,它把 1 映射到某个集合 a,并把零元运算映射到一个函数:

raise :: () -> a

在 Scala 中:

def raise: Unit => A

可以用余端公式恢复 Maybe 单子。考虑加入零元运算对 hom-set L(n, 1) 做了什么。除了创建新的 L(0, 1)(它在 F^op 中不存在),它还会向 L(n, 1) 添加新的态射。这些态射是把形如 n -> 0 的态射与我们的 0 -> 1 组合得到的结果。在余端公式中,这些贡献都会与 a^0 x L(0, 1) 识别起来,因为它们可以从:

a^n x L(0, 1)

出发,通过两种不同方式提升 0 -> n 得到。

a^n x L(0, 1) --<f,id>--> a^0 x L(0, 1)
a^n x L(0, 1) --<id,f>--> a^n x L(n, 1)

这个余端化简为:

T_L a = a^0 + a^1

或者用 Haskell 记法:

type Maybe a = Either () a

Scala 中则是:

type Option[A] = Either[Unit, A]

它等价于:

data Maybe a = Nothing | Just a

Scala 中:

sealed trait Option[+A]
case object None extends Option[Nothing]
case class Some[A](a: A) extends Option[A]

注意,这个 Lawvere 理论只支持抛出异常,不支持处理异常。

30.8 挑战(Challenges)

  1. 枚举 FFinSet 的骨架)中 23 之间的所有态射。
  2. 证明幺半群 Lawvere 理论的模型范畴等价于 list 单子的单子代数范畴。
  3. 幺半群的 Lawvere 理论生成 list 单子。证明它的二元运算可以用相应的 Kleisli 箭头生成。
  4. FinSetSet 的子范畴,并且存在一个把它嵌入 Set 的函子。Set 上任意函子都可以限制到 FinSet。证明有限型函子是其自身限制的左 Kan 扩张。

30.9 延伸阅读(Further Reading)

  1. F. William Lawvere, Functorial Semantics of Algebraic Theories
  2. Gordon Plotkin and John Power, Notions of computation determine monads