原文链接:Category Theory for Programmers: The Preface

很长一段时间以来我一直有一个大胆的想法:写一本面向程序员的范畴论小册子。注意:不是计算机科学家,而是程序员——工程师而不是科学家。我知道这很疯狂,连我自己都感到害怕。不可否认,科学界和工程界存在着巨大的代沟:我在两个领域都有工作过。但重铸科普荣光,我辈义不容辞。理查德·费曼一直是我的偶像,他总是可以把事情解释得通俗易懂。我知道我不是费曼,但我会努力的!首先我们先来写一个序言——我们要激发读者对学习范畴论的热情和斗志——也希望可以引发讨论,也为我收集一些反馈。

我会尝试着在接下来的几个段落里说服你学习这本专门为你量身定做的范畴论小册子,无论你有多么不想在你丰富的课余时间学习它——这可是数学领域中最抽象的分支之一。

我的自信来源于我的观察。首先,范畴论(译注:直白一点可以叫它分类论)是程序设计的宝库。Haskell程序员已经在这个宝库里偷着乐了很久了,这些点子也在慢慢地打入其他编程语言内部。但是这个过程实在是太慢了,我们需要提提速。

其次,数学有很多的分支,每个分支所针对的受众也不同。你可能一看到微积分和代数就想吐,但这并不意味着你无法享受😎范畴论。我会尽可能地告诉你范畴论和程序员的脑子究竟有多契合。因为范畴论无关乎细节,而更看重结构——看重使程序可组合的结构。

组合是范畴论中最基本的概念——它本身就是“类别”(原文:category)定义的一部分。组合同时也是编程中重要的一环。早在一些伟大的工程师们提出子程序之前的史前时代,程序“猿”们就在不停地缝合了。曾几何时结构化编程的概念改变了编程,使得代码块之间可以相互组合。然后就是面向对象编程,完全就是代码组合。函数式编程不仅仅只是组合函数和代数数据结构——它甚至可以组合并发——这在其他的编程范式看来几乎是不可能的事情。

此外,我有一把秘密武器,可以庖丁解牛的刀,能将数学分解为程序员们更容易接受的美味上脑。如果你是一个专业的数学家,你就得小心地弄清你的一切假设,修正你的陈述,最后构造严密的证明。这使得理解数学系的文章和书籍对于非数学系的麻瓜们(包括译者)难于登天。我是一个受过专业训练的物理学家。在物理学领域里,我们会一般先做一些非正式的推理,除非忍不住。数学家们嘲笑狄拉克δ函数(译注:一个程序员可能更熟悉的翻译是“冲激函数”)不过是物理学家P. A. M. 狄拉克脑补出来求解一些微分方程的。然而微积分的一个全新分支——分布理论——被发现后,狄拉克狠狠抽肿了他们的脸。

当然,章口就来很容易导致一些一眼假的错误。所以在这本小册子里我会尽可能保证这些非正式的说明背后都有强大的数学理论支撑。我有供着一本桑德斯·麦克莱恩(Saunders Mac Lane)的《职业数学家范畴论》(Category Theory for the Working Mathematician)的!

因为这是写给程序员的,因此我会使用代码说明一些核心概念。你可能已经听说了“没有语言比函数式语言更贴近数学”。函数式语言还提供了更多抽象的力量。因此你可能理所当然地认为:不会Haskell学什么范畴论?但这样就给大家留下了“范畴论就是拿来搞函数式编程的”这样的刻板印象。为此,我还专门提供了大量的C++代码。那么代价是什么呢?你必须捏着鼻子读完某些糟糕的C++语法,冗长的铺垫代码,被迫实现一些复制粘贴……写了这么多年C++,你们应该已经习惯了吧?

你也不用急着和Haskell说掰掰。你不需要成为Haskell领域大神,只要可以用Haskell"画饼"(译注:原文为sketching and documenting ideas)就好了。我也是这么学习Haskell的。Haskell简洁的语法和强大的类型系统可以帮助你更好地理解和实现C++模板,数据结构和算法。既然我假定大家可能不会Haskell,我会在后续内容中慢慢解释给大家。

如果你已经是计算机领域大神了,你可能会问:我写了这么多年代码,也没有学过什么范畴论,我就问你影响在哪?你肯定已经在各种命令式语言(imperative languages)里发现了各种“文化入侵”。哪怕是嘴最硬的Java和C++都在疯狂加入lambda表达式以跟上时代的潮流。一切量变都是为了引起质变——或者用物理学家的说法,相变(phase transition)。如果你在烧水,那么水总有一天会开的。我们现在就是在温水里的青蛙:是继续畅游温泉,还是跳出这口锅。

在烧水器中,火力开得最大的之一当属多核计算(译注:怪不得皮衣黄的显卡越来越像煤气灶)。网红编程范式——面向对象编程——在并发和并行计算上纯属麻瓜,只会给你留下一堆bug。数据隐藏(data hiding),面向对象的基本前提,在数据共享和可变性的作用下,成为了数据竞争的温床。互斥量(mutex)确实有效,但是没法组合,而且会导致死锁这种要命的问题。

退一万步,就算没有并发,日益复杂的软件系统无时无刻不在试探命令式编程的底线。简而言之,副作用正在失控。确实,副作用代码很方便很好用,你只需要给你的变量取个好名字,加几行注释就好了。一个名字叫SetPassword或者WriteFile的函数一眼就知道有副作用,这已经司空见惯了。然而当我们把有副作用的函数组合起来的时候,一切就开始变得焦头烂额了起来。并不是说副作用坏坏——它们只是被隐藏了起来导致难以大规模管理。副作用本身并不会扩展,命令式编程就是做这个的。

硬件的进步和软件复杂性的提升敦促我们重新思考编程的本质。就像欧洲伟大的哥特式大教堂的建造者一样,我们一直在将我们的工艺磨练到材料和结构的极限。法国博韦有一座未完工的哥特式大教堂,见证了人类与局限性的深刻斗争。它的目的是打破以前所有的高度和重量记录,可惜建筑物是有极限的。人们采取了很多临时措施,例如用铁棍和木头来支撑,但这不过是亡羊补牢。从现代人的角度来说,这些哥特式建筑的完工简直就是奇迹:他们那时候哪有什么现代材料科学,计算机建模,有限元分析以及它们相应的数学和物理知识?我希望我们的后代也会如此赞赏我们在构建复杂操作系统、网站服务器以及网络基建时展现出来的高超编程技巧。他们当然会的:因为我们现在所做的一切也基于一套脆弱的理论基础。现在是时候夯实基础,向前迈进了。