<译> 天然变换

原文:https://bartoszmilewski.com/2...c++

上一篇:函数类型程序员

译注:因为距离以前的翻译的时间过久,因此有些内容可能有点不太相符。待我找时间校对。编程

咱们已经讨论过,函子能够在维持范畴结构的前提下实现范畴之间的映射。函子能够将一个范畴嵌入到另外一个范畴,它也可让多个范畴坍缩为一个范畴且不会破坏范畴的结构。凭借函子,咱们能够在一个范畴以内构建另外一个范畴。源范畴可视为目标范畴的部分结构的模型或蓝图。segmentfault

范畴的嵌入

将一个范畴嵌入到另外一个范畴可能有许多种方式。有时这些方式是等价的,有时它们不等价。你能够将整个的范畴坍缩为另外一个范畴中的一个对象,也能够将一个范畴中的每一个对象映射为另外一个范畴中不一样的对象,将前者中的每一个态射映射为后者中的不一样的态射。一样的想法能够有多种不一样方式的实现。天然变换能够帮助咱们对比这些实现。天然变换是函子之间的映射——能够保持函子性质不变的特殊映射。函数

对于范畴 CD 之间的两个函子 FG,若是咱们只关注 C 中的一个对象 a,它被映射为 D 中的两个对象:F aG a,那么应该存在一个函子映射,它能够将 F a 映射为 G a测试

函子映射

因为在同一范畴中的对象映射应该不会脱离该范畴,并且咱们不想人工创建 F aG a 的联系,所以很天然的考虑使用既有的联系——所谓的态射。天然变换本质上是如何选取态射:对于任意对象 $a$,天然变换就是选取一个从 F aG a 的态射。若是将一个天然变换称为 α,那么这个态射就被称为在 a 上的 α 份量(Component of α at a),记做 α_a优化

α_a :: F a -> G a

译注: α_a 表示 $\alpha_a$。用 HTML 标记来表示,就是 α<sub>a</sub>。因为许多号称支持 Markdown 文档格式的网站,它们不支持在 Markdown 里自由使用 HTML 标记。虽然这些网站有一些也支持 TeX 公式,可是又无法在代码排版环境中嵌入 TeX 公式。所以,我不得不使用下划线来表示公式里的下标。在下文,我也会使用 ^ 来表示上标,顺便说一句,我对如今几乎任何一个由用户自行发布内容的网站都不满意。网站

记住,a 是一个在 C 中的对象,而 α_aD 中的一个态射。spa

对于某个 a,若是在 F aG a 之间没有态射,那么 FG 之间也就不存在天然变换。翻译

故事刚刚说了一半。函子所映射的不止是对象,它们也能映射态射,天然变换应该如何对待这种映射?答案是,态射的映射是固定的——在 FG 之间的任何一个天然变换下,F f 必须变换成 G f。也就是说,两个函子对态射所造成的映射会完全限制与之相适的天然变换的定义。来考虑在范畴 C 中的两个对象 ab 之间的态射 f,它被映射为范畴 D 中的两个态射 F fG f

F f :: F a -> F b
G f :: G a -> G b

天然变换 α 提供两个附加的态射来补全 D 中的结构:

α_a :: F a -> G a
α_b :: F b -> G b

D 中的结构

如今,咱们便有了两个从 F aG b 的途径。为了保证这两种途径等价,必须引入天然性条件(Naturality condition):

G f ∘ α_a = α_b ∘ F f

这个条件应该对于任意的 f 都成立。

天然性条件很是有用。例如,若是态射 F f 是可逆的,天然性决定了以 α_a 形式表示的 α_b。经过 f,基于天然性条件可将 α_a 变换为:

α_b = (G f) ∘ α_a ∘ (F f)^(-1)>

天然性条件的做用

若是两个对象之间存在多个可逆的态射,上述变换也都成立。尽管态射一般是不可逆的,可是我想说的是两个函子之间的天然变换并不是必定存在。所以,与天然变换相关的函子的多寡,可在很大程度上显现这些函子所操纵的范畴的结构。在讲极限与 Yoneda 定理时,我会给出一些例子。

从份量的角度来看天然变换,您能够认为它将对象映射为态射。可是,从天然性条件的角度来看,你也能够认为它将态射映射为四方形的交换图(Commuting squares)—— C 中的每一个态射都被映射为 D 中的一个交换图。

天然性

天然变换的这一性质可让不少范畴便于构造,这些范畴每每包含着这类的交换图。在正确选择函子的状况下,大量的交换条件都可以转换为天然性条件。之后在讲到极限、上极限(Colimit)以及伴随(Adjunction)时会给出一些例子。

最后,天然变换可用于定义函子的同构。若是说两个函子是天然同构的,差很少是在是说它们是相同的函子。天然同构是以天然变换的形式定义的,这种天然变换的各个份量都是同构的(可逆的态射)。

多态函数

以前讲过函子(更确切的说,是自函子)在编程中所扮演的角色。它们至关于类型构造子——将类型映射为类型。不过,函子也能将函数映射为函数,这种映射是经过一个高阶函数 fmap(在 C++ 中则是 transform, then 之类的行为)。

为了构造一个天然变换,咱们从一个对象开始,也就是一种类型,设为 a。一个函子 F,能够将 a 映射为类型 F a。另外一个函子 G,能够将 a 映射为 G a。在 a 上的天然变换 alpha 的份量是一个从 F aG a 的函数。使用用伪 Haskell 代码,可将其表示为:

alpha_a :: F a -> G a

天然变换是面向各类类型 a 的多态函数:

alpha :: forall a . F a -> G a

forall a 在 Haskell 中是可选的(能够用语言扩展 ExplicitForAll 开启它)。一般,可将其写为:

alpha :: F a -> G a

请记住,这是由 a 参数化的一个函数族。这是 Haskell 语法简洁性的又一个示例。在 C++ 中,与之相似的构造要麻烦一些:

template<Class A> G<A> alpha(F<A>);

Haskell 的多态函数与 C++ 的泛型函数之间存在很大的区别,主要体现为函数的实现方式以及类型检查方式上。在 Haskell 中,一个多态函数必须对于全部类型是惟一的。一个公式必须适用于全部的类型。这就是所谓的参数化多态(Parametric polymorphism)。

C++ 默认提供的是特设多态(Ad hoc polymorphism),这意味着模板不必定涵盖全部类型。一份模板是否适用于某种给定的类型须要在实例化时方能肯定,彼时,编译器会用一种具体的类型来替换模板的类型参数。类型检测是以推导的形式实现的,编译器常常会给出难以理解的错误信息。

在 C++ 中,还有一种函数重载与模板特化机制,经过这种机制能够为不一样的类型定义函数的不一样版本。Haskell 也有相似的机制,即类型类(Type class)与类型族(Type family)。

Haskell 的参数化多态有一个一个不可预料的结果。凡是像这种类型的多态函数:

alpha :: F a -> G a

函子 FG 自动知足天然性条件。这里,咱们再回到范畴论的概念(f 是一个函数,f::a->b):

G f ∘ α_a = α_b ∘ F f

在 Haskell 中,函子 G 做用于一个态射 f 是经过 fmap 实现的。可以使用 Haskell 伪码将上述概念表示为:

fmap_G f . alphaa = alphab . fmap_F f

归功于 Haskell 的类型推导,上述类型标记是不须要的,所以可写为如下形式:

fmap f . alpha = alpha . fmap f

这依然不是真正的 Haskell 代码——在代码中没法表示函数等式——不过,上式是恒等的,程序员在等式推导中可使用这个公式,此外,编译器也能够利用这个公式对代码进行优化。

天然性条件之因此在 Haskell 里会自动被知足,这是『免费的定理』的天然结果。在 Haskell 里,参数化多态,将其用于定义天然变换,会引入很是强的限制条件——一个公式适应全部类型。这些限制条件会变成面向这些函数的方程同样的定理。对于可以对函子进行变换的函数,免费的定理是天然性条件。(做者注:你能够阅读我写的另外一篇文章《Parametricity: Money for Nothing and Theorems for Free》,会让你对这些免费的定理可以了解得更多一些。)

以前我提到过,在 Haskell 里,能够将函子视为泛型容器。咱们能够继续这个类比,将天然变换视为一种重组方法,即将一个容器里的东西取出来放到另外一个容器里。咱们不会触碰这些东西:不修改,也不创造。咱们只是将它们(或它们的一部分)复制到新的容器里,在这个过程当中有时候会对它们做几回乘法。

因而,天然性条件就是,首先它不关心咱们是先经过 fmap 修改这些东西,而后再将它们放到新容器里,仍是先把它们放到新容器里再用适用于这个容器的 fmap 去修改它们。重组与 fmap,它们是正交的,『你走你的阳关道,我过个人独木桥』。

来看一下 Haskell 里的天然变换。首先来看列表与 Maybe 这两种函子之间的天然变换,它的功能是,当且仅当列表非空时返回列表的首元素:

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x

它是面向 a 的函数多态化。它能够不受限制地做用于任意一种类型 a,所以它是一个参数化多态的例子。从而,它就是两个函子之间的一个天然变换。不过,如今这只是咱们在自觉得是,下面来验证它是否符合天然性条件。

fmap f . safeHead = safeHead . fmap f

咱们要考虑两种状况;一个空列表:

fmap f (safeHead []) = fmap f Nothing = Nothing

safeHead (fmap f []) = safeHead [] = Nothing

和一个非空列表:

fmap f (safeHead (x:xs)) = fmap f (Just x) = Just (f x)

safeHead (fmap f (x:xs)) = safeHead (f x : fmap f xs) = Just (f x)

上面我动用了面向列表的 fmap

fmap f [] = []
fmap f (x:xs) = f x : fmap f xs

与面向 Maybefmap

fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)

当函子之一是微不足道的 Const 函子的时候,会发生一件有趣的事。一个以 Const 函子为始点或终点的天然变换,看上去就像一个函数,它即面向它的返回类型多态,也面向它的参数类型多态。

例如,可将 length 视为从列表函子到 Const Int 函子的天然变换:

length :: [a] -> Const Int a
length [] = Const 0
length (x:xs) = Const (1 + unConst (length xs))

这里,unConst 用于剥除 Const 构造子:

unConst :: Const c a -> c
unConst (Const x) = x

固然了,实际上 length 的定义是下面这样:

length :: [a] -> Int

这个定义有效地掩盖了 length 做为天然变换的本质。

寻找一个以 Const 函子为始点的参数化多态函数有点难,由于这须要无中生有创造一个值出来。咱们所能想到的最好的办法是:

scam :: Const Int a -> Maybe a
scam (Const x) = Nothing

还有一个不一样寻常的函子,咱们以前已经见过它了,它在 Yoneda 引理中扮演了重要的角色。这个函子就是 Reader 函子。下面我用 newtype 来重写一下它的定义:

newtype Reader e a = Reader (e -> a)

这个函子被两种类型参数化了,可是它的(逆变)函子性仅着落在第二个类型上:

instance Functor (Reader e) where  
    fmap f (Reader g) = Reader (\x -> f (g x))

对于每种类型 e,均可以定义从 Reader e 到任何其余函子的天然变换家族。之后会看到,这个家族的成员老是与 f e 的元素壹壹对应(Yoneda 引理)。

例如,考虑有时会被忽略的仅有一个值 () 的 unit 类型 ()。函子 Reader () 接受任何一种类型 a,将它射入函数类型 () -> a。这些函数能够从集合 a 中拮取一个元素。这些函数的数量与 a 中元素的数量同样多。如今,来看一下从这种函子到 Maybe 函子的天然变换:

alpha :: Reader () a -> Maybe a

这样的天然变换只有 dumb

dumb (Reader _) = Nothing

obvious

obvious (Reader g) = Just (g ())

(用 g 能作的事情仅仅是让它做用于 ()。)

还有,实际上按照 Yoneda 引理的说法,这些与 Maybe () 类型的两个元素相符,即 NothingJust ()。呆会儿咱们就会再回到 Yoneda 引理上来——以上的说法有些不严肃。

超天然性

两个函子之间的参数化多态函数(包括 Const 函子这种边界状况)一定是天然变换。由于全部的标准代数数据类型都是函子,在这些类型之间的任何一个多态函数都是天然变换。

咱们还掌握了函数类型,它们对于它们的返回类型而言具备着函子性。咱们可使用它们来构造函子(例如 Reader 函子),并为这些函子构造天然变换——更高阶的函数。

不过,对于参数类型而言,函数类型不具有协变性,它们具有逆变性。固然,逆变函子就是相反范畴中的协变函子。在范畴意义上,两个逆变函子之间的多态函数依然可视为天然变换,除了它们只能做用于 Haskell 类型所构成的两个相反的范畴里的函子。

你可能还记得以前咱们见过的一个逆变函子的示例:

newtype Op r a = Op (a -> r)

这个函子对于 a 而言具备逆变性:

instance Contravariant (Op r) where
    contramap f (Op g) = Op (g . f)

咱们能够写一个函数,假设它从 Op BoolOp String

predToStr (Op f) = Op (\x -> if f x then "T" else "F")

因为这两个函子不具有协变性,它并不是 Hask 范畴中的天然变换。不过,因为它们都具有逆变性,因此它们知足『相反的』天然性条件:

contramap f . predToStr = predToStr . contramap f

注意,函数 f 必须得走与 fmap 的做用下的方向相反的方向,由于 contramap 的签名是:

contramap :: (b -> a) -> (Op Bool a -> Op Bool b)

存在不是函子的类型构造子吗,它是协变的仍是逆变的?看下面的例子:

a -> a

这不是一个函子,由于同一类型的 a 出如今负(逆变)位与正(协变)位上。对于这种类型,fmapcontramap 都没法实现。所以,函数签名:

(a -> a) -> f a

其中 f 是任意函子,这个函数不是天然变换。有趣的是,存在着一种广义的天然变换,叫做双天然变换,它们可以处理这些状况。在咱们讨论端(End)的时候会遇到它们。

函子范畴

如今,咱们有了函子之间的映射——天然变换——所以很天然地就会想到,函子是否可以造成范畴?没错,它们能够。对于每对范畴而言,仅存在一个函子范畴。在这个范畴里,对象是从 C 到 D 的函子,而态射就是这些函子之间的天然变换。

咱们必须得定义两个天然变换的复合,不过,这至关容易。天然变换的份量是态射,而咱们知道怎样实现态射的复合。

没错,咱们以从函子 F 到函子 G 的天然变换 α 为例。它在对象 a 上的份量是某个态射:

α_a :: F a -> G a

咱们打算用对 αβ 进行复合,后者是从 GH 的天然变换。βa 上的份量是一个态射:

β_a :: G a -> H a

这些态射是可复合的,复合结果又是一个态射:

β_a ∘ α_a :: F a -> H a

能够用这个态射做为天然变换 $β\cdot α$ 的份量——天然变换 βa 以后的复合:

(β ⋅ α)_a = β_a ∘ α_a

天然变换的复合

仔细观察上面这幅图,能够确信这种复合的结果是从 FH 的天然变换:

H f ∘ (β ⋅ α)_a = (β ⋅ α)_b ∘ F f

天然变换复合的全貌

天然变换的复合遵循结合律,由于它们的份量都是常规态射,然后者的复合是遵循结合律的。

最后,对于每一个函子 F,存在一个恒等天然变换 1_F,它的份量是恒等态射:

id_{F a} :: F a -> F a

因此,函子的确能造成范畴。

说一说记法。与 Saunders Mac Lane 同样,上面我使用小圆点(dot)来表示各类天然变换的复合。问题是存在两种天然变换的复合方式。一种叫竖向复合,由于在示意图里,函子一般是往下堆砌的。竖向复合对于定义函子范畴很重要。下面我简短介绍一下横向复合。

竖向复合与横向复合

范畴 C 与 D 之间的函子范畴记为 Fun(C, D)[C, D],有时也写成 D^C。最后这种记法暗示了可将函子范畴自己视为其余范畴里的一个函数对象(指数)。其实是这样吗?

看一下咱们到如今为止所构建的抽象层。咱们从一个范畴开始,它由一组对象与态射构成。范畴自己(或严格地说是小范畴,它们的对象造成集合)是更高层的范畴 Cat 里的对象。在 Cat 里,态射是函子。Cat 里的 Hom-集是函子构成的集合。例如,Cat(C,D) 是范畴 C 与 D 之间的函子集合。

Cat 范畴

函子范畴 [C, D] 也是两个范畴之间的函子集合(加上天然变换为态射)。它里面的对象也是 Cat(C,D) 里的东西。此外,函子范畴是范畴,它自己必须得是 Cat 里面的对象之一(也就是说,两个小范畴之间的函子范畴自己也很小)。一个范畴里的 Hom-集与同一个范畴里的对象之间存在联系。这种状况就像咱们在上一节里所看到的指数形式的对象。如今来看一下,Cat 里如何构造后者。

你可能还记得,为了构造一个指数,须要首先定义积。在 Cat 里,定义积至关容易,由于小范畴是对象的集合,而咱们又知道怎样定义集合的笛卡尔积。所以,积范畴 C × D 里的一个对象,是两个对象构成的序对 (c, d),一个来自 C,一个来自 D。相似地,在这样的序对 (c, d)(c', d') 之间的态射是一个态射序对 (f, g),其中 f :: c -> c'g :: d -> d'。因为这些态射序对由 CD 中态射组成,所以老是会有一个由 CD 中的恒等态射构成的序对。长话短说,Cat 是一个彻底的笛卡尔闭范畴,对于任意一对范畴(译注:例如 CD),它里面存在着相应的指数对象 D^C。因为我说过, Cat 里的对象是范畴,所以 D^C 是范畴,它就是 CD 之间的函子范畴。

2-范畴

问题解决了,咱们如今近观一下 Cat。根据定义,Cat 里的任意 Hom-集都是函子集合。可是,就像咱们见识过的,两个对象之间的函子有着比集合更丰满的结构。它们造成一个范畴,以天然变换为态射。因为在 Cat 里,函子被认为是态射,天然变换就是态射之间的态射。

这个更丰满的结构是 2-范畴的一个例子。2 -范畴是一个广义的范畴,其中,除了对象和态射(这里应该叫它 1-态射)以外,还有 2-态射,它就是态射之间的态射。

Cat 的 2-范畴具备:

  • 对象:(小)范畴
  • 1-态射:范畴之间的函子
  • 2-态射:函子之间的天然变换

咱们用 Hom-范畴——函子范畴 D^C 来代替范畴 CD 之间的 Hom-集。咱们有常规的函子复合:来自 D^C 的函子 F 与来自 E^D 的函子 G 复合,能够获得来自 E^C 的函子 G ∘ F。可是,在每一个 Hom-范畴内部,也存在着复合——函子之间天然变换或 2-态射的竖向复合。

竖向复合

使用 2-范畴里的两种复合方法,问题升级为:它们之间有何关系?

咱们先选在 Cat 里选两个函子,或者 1-态射:

F :: C -> D
G :: D -> E

与它们的复合:

G ∘ F :: C -> E

假设咱们有两个天然变换,αβ,它们分别做用于函子 FG

α :: F -> F'
β :: G -> G'

注意,咱们不能对这两个天然变换应用竖向复合,由于 α 的终点与 β 的始点不重合。实际上,它们分别属于两个不一样的函子范畴 D^CE^D。不过,咱们可以复合函子 F'G',由于 F' 的终点与 G' 的起点重合——它就是范畴 D。函子 G’∘ F’G ∘ F 之间有什么关系呢?

如今咱们手里有 αβ,可不能够定义一个从 G ∘ FG’∘ F’ 的天然变换?咱们画个草图看看:

往常,咱们从 C 中的一个对象 a 开始。它分裂为两个 D 中的对象:F aF' a。还有一个态射,α 的一个份量,链接这两个对象:

α_a :: F a -> F'a

在从 DE 的时候,这两个对象进一步分裂为四个对象:

G (F a), G'(F a), G (F'a), G'(F'a)

咱们还有 4 个态射,它们造成了一个方格。这些态射中有两个是天然变换 β 的份量:

β_{F a} :: G (F a) -> G'(F a)
β_{F'a} :: G (F'a) -> G'(F'a)

另外两个则是 α_a 在两个函子下的图像(函子映射了态射):

G α_a :: G (F a) -> G (F'a)
G'α_a :: G'(F a) -> G'(F'a)

好多态射。咱们的目标是寻找从 G (F a)G'(F'a) 的态射。一个候选解是链接两个函子 G ∘ FG’∘ F’ 的天然变换的份量。事实上,从
G (F a)G'(F'a) 的路径不是一条,而是两条:

G'α_a ∘ β_{F a}
β_{F'a} ∘ G α_a

幸亏,它们相等。由于这四个态射所造成的方格对于 β 而言具备天然性。

咱们刚才已经定义了从 G ∘ FG’∘ F’ 的天然变换的一个份量。假若你足够有耐心,那么这个变换的天然性证实至关直观。

咱们将这个天然变换称为 αβ 的横向复合:

β ∘ α :: G ∘ F -> G'∘ F'

再一次沿用 Mac Lane 的记法,我使用小圆圈来表示横向复合。在它出现的位置上,你也可能看到的是星号。

有一个范畴化的经验法则:每次拿到复合时,应该去找一个范畴。咱们有天然变换的竖向复合,它是函子范畴的一部分。那么,横向复合呢?它身居什么范畴里?

要获得答案,须要从侧面来看 Cat。不要将天然变换当作函子之间的箭头,而是将它们当作范畴之间的箭头。一个天然变换位于两个范畴之间,而这两个范畴本来是由这个天然变换所变换的函子链接的。咱们能够认为这个天然变换链接着这两个范畴。

如今,咱们把注意力放在 Cat 里的两个对象上,即范畴 CD。存在着由天然变换构成的集合,这些天然变换来往于链接 CD 的函子之间。这些天然变换就是咱们从 CD 的新箭头。同理,也有一些天然变换来往于链接 DE 的函子之间的天然变换,咱们将它们视从 DE 的新箭头。横向复合,就是这些箭头的复合。

咱们也有一个从 CC 的恒等箭头。它是 C 上的恒等函子自身的恒等天然变换。注意,横向复合的恒等也是竖向复合的恒等,可是反过来却不是。

最后,这两种复合知足交换律:

(β' ⋅ α') ∘ (β ⋅ α) = (β' ∘ β) ⋅ (α' ∘ α)

在此,我引用 Saunders Mac Lane 的说法:读者可能以为绘制显而易见的示意图要比去证实它更有趣。

之后还会有更多的记法。在这个从侧面看 Cat 的新解释里,从一个对象到另外一个对象存在两种方法:使用函子或使用天然变换。然而,咱们能够将函子箭头从新解释为一种特殊的天然变换:恒等天然变换做用于这个函子。所以,你将会常常遇到这种记号:

F ∘ α

其中,F 是从 DE 的函子,而 α 是从 CD 的两个函子之间的天然变换。由于你不能将用一个天然变换去与一个函子进行复合,因此这个记号须要解读为恒等天然变换 1_F 位于 α 以后的横向复合。

相似地:

α ∘ F

α 位于 1_F 以后的横向复合。

总结

这是对这本书第一部分的总结。咱们已经了解了范畴论的基本术语。你可能会这样认为:对象与范畴是名词;态射、函子以及天然变换是动词。态射链接了对象,函子链接了范畴,天然变换链接了函子。

可是咱们已经看到了,一个抽象层上的一个动做,在下一个抽象层次上就变成了一个对象。态射的集合变成了函数对象。做为对象,它能够是另外一个态射的始点或终点。这就是高阶函数背后的思想。

函子,将对象映射为对象,所以咱们将其做为类型构造子,或者做为一种参数化的类型。函子,也能将态射映射为态射,所以它也是高阶函数——fmap。有一些简单的函子,例如 Const、积以及余积,它们能够产生大量的代数数据类型。函数类型也具备函子性,协变性与逆变性,它们能够用于扩充代数数据类型。

函子在函子范畴里能够视为对象。这样,它们就变成了态射的始点与终点,因而有了天然变换。天然变换就是特定形式的多态函数。

挑战

  1. 定义从 Maybe 到列表函子的天然变换,并证实它符合天然性条件。
  2. Reader () 与列表函子构造至少两个不一样的天然变换。存在多少个不一样的 () 列表?
  3. Reader BoolMaybe 来作上一个练习。
  4. 揭示天然变换的横向复合知足天然性条件(提示:使用份量)。对于追求示意图的人而言,这是个很好的练习。
  5. 写一篇文章,谈谈你是如何以为画显而易见的示意图赛过证实交换律。
  6. 为不一样的 Op 函子之间的变换的相反天然性条件,建立一些测试案例。如下是一个示例:
op :: Op Bool Int
op = Op (\x -> x > 0)

f :: String -> Int
f x = read x

致谢

感谢 Gershom Bazerman 检查了个人数学和逻辑,也感谢 André van Meulebrouck 在编辑方面的帮助。

$$ \cdot $$

译注:做者后面还有两部份内容,可是我决定只翻译到这里。另外,这篇文章的原文下面的评论区,一位网名「benjaminy」的人的评论,也值得思考。另外,做者在这第一部分所讲述的这些范畴论概念,已经足以让你在必定程度上了解单子——自函子范畴上的一个幺半群。