函数式编程的数学基础(七)简单类型论

Kleene-Rosser悖论

构造出Y组合子几乎证明了lambda系统具有图灵完备性,尽管在当年这样的理论发展还不完善。

但是任何具有图灵完备性的系统必定会受到图灵停机问题的困扰,这个问题的lambda版本就是 Kleene-Rosser 悖论。

为了便于理解,这里我们不采用原版的构造方法,我们会引入一些简化的概念来构造Kleene-Rosser悖论。

我们以Y组合子构造一个函数f:
f = λ x . ( NOT ( f x ) ) f = \lambda x.(\text{NOT}\ (f\ x)) f=λx.(NOT (f x))

可以发现,我们使得一个递归函数返回自身的否定,那么将会得到一个自相矛盾的函数。

这就是 Kleene-Rosser 悖论,它的存在导致 lambda 系统自身的不一致性。也导致包括邱奇本人、Rosser、Haskell等诸多优秀的数学和逻辑学家投入大量精力致力于修复这个问题。

但是在学术研究领域,有问题不一定是坏事,解决问题的过程中,诞生的新方法、新理论,可能价值反而超过解决问题本身。

Kleene-Rosser 悖论就是一个典型的案例。

简单类型论

为了解决Kleene-Rosser悖论,邱奇提出了为lambda函数引入类型。我们简要描述如下:

  • 基本类型:如 Integer、Boolean 这样的基本类型。其值可以自行定义,通常来自一些数学概念。
  • 函数类型:如 A -> B 这样的函数类型,部分常量函数可以自行定义,通常来自一些数学概念,亦可能由已有的类型通过lambda运算构成。
  • 类型运算:
    • 乘法类型 A×B,可理解为元组
    • 加法类型 A+B,可以理解为类型的或关系

在这样的定义下,任何lambda表达式都有确定的类型。

通过上述类型定义,一方面,lambda演算可以作为工具,给其它数学分支使用,另一方面因为无法构造不动点,不能产生递归类型,这也就解决了Kleene-Rosser悖论。但正因为无法构造不动点,带有简单类型的lambda演算不再是图灵完备的。后世又有诸多数学家、计算机学家尝试在简单类型论的基础上恢复图灵完备性,那是后话,暂且不表。

即使失去了图灵完备性,简单类型lambda演算也拥有巨大的潜力。由 Haskell Curry 提出,并由 Howard 发展的 Curry-Howard 同构,揭示了简单类型论与命题逻辑之间的深刻联系。

Curry-Howard 同构

为了理解 Curry-Howard 同构,我们首先来简单复习一下命题逻辑。

命题逻辑是一组基本的逻辑工具。它定义了"命题"、"与"、"或"、"蕴含"等概念。

例如,如果我们有三个命题A、B、C,现有两个证明
p 1 : A → B (若 A 则 B ) p 2 : B → C (若 B 则 C ) p1:A \rightarrow B(若A则B) \\ p2:B \rightarrow C(若B则C) p1:A→B(若A则B)p2:B→C(若B则C)

则可以构造出证明
p 3 : A → C (若 A 则 C ) p3:A \rightarrow C(若A则C) p3:A→C(若A则C)

而我们考虑在带类型的lambda中,如果有两个函数:
f 1 = λ a . b : A → B f 2 = λ b . c : B → C f_1 = \lambda a.b: A \rightarrow B \\ f_2 = \lambda b.c: B \rightarrow C \\ f1=λa.b:A→Bf2=λb.c:B→C

通过此二函数的复合运算可以构造出函数f
f = f 2 ∘ f 1 = λ a . ( f 2 ( f 1 a ) ) : A → C f = f_2 \circ f_1 = \lambda a.(f_2\ (f_1\ a)): A \rightarrow C f=f2∘f1=λa.(f2 (f1 a)):A→C

Haskell Curry 发现命题逻辑和类型构造之间具有一种奇妙的联系:

在一个简单类型系统中,我们有一组对应于公理的简单类型 ( A 、 A → C 、 A + B . . . . . . ) (A、A \rightarrow C、A+B ......) (A、A→C、A+B......),如果我们能构造出构造出一些新类型 ( B 、 C 、 A → B . . . . . . ) (B、C、A \rightarrow B......) (B、C、A→B......)的表达式,那么我们也能在命题逻辑中证明这些新类型对应的命题。

基本的命题逻辑符号和类型论符号的对应关系如下:

文字描述 数学符号 类型符号 编程语言(JavaScript)
命题T T T T
A且B (析取) A ∧ B A\ ∧\ B A ∧ B A + B A+B A+B A | B
A或B (合取) A ∨ B A\ ∨\ B A ∨ B A × B A \times B A×B A, B
若A则B(蕴含) A → B A\ \rightarrow\ B A → B A → B A \rightarrow B A→B A => B

lambda演算的β归约 对应于逻辑系统的分离规则

分离规则(MP) :如果有 A A A 和 A → B A \rightarrow B A→B ,那么有 B B B 。

简单类型论中的基本类型,对应于在逻辑系统中的公理。

简单类型论中构造一个新的类型,对应于在逻辑系统中推导出一个新的定理。

编写一个lambda演算的表达式,对应于在逻辑系统中进行推导。

Curry-Howard 同构揭示了命题逻辑与带类型lambda演算之间的深刻联系,它也为型论脱离lambda体系发展打下了基础。

带类型的组合子逻辑

我们前文已经证明了组合子逻辑与lambda演算的等价性,那么简单类型论应用于组合子逻辑,Curry-Howard 同构会展现出什么样的特性呢?

我们考虑SKI系统的类型:
I = λ x . x : X → X K = λ x . λ y . x : X → ( Y → X ) S = λ x . λ y . λ z . ( x z ( y z ) ) : ( X → ( Y → Z ) ) → ( ( X → Y ) → ( X → Z ) ) \begin{array}{ll} I = \lambda x.x & : X \rightarrow X \\ K = \lambda x.\lambda y.x & : X \rightarrow (Y \rightarrow X) \\ S = \lambda x.\lambda y.\lambda z.(x\ z\ (y\ z)) & : (X \rightarrow (Y \rightarrow Z))\rightarrow((X \rightarrow Y)\rightarrow (X \rightarrow Z)) \\ \end{array} I=λx.xK=λx.λy.xS=λx.λy.λz.(x z (y z)):X→X:X→(Y→X):(X→(Y→Z))→((X→Y)→(X→Z))

若将SK的类型对应到命题逻辑,我们可以发现,它刚好是希尔伯特公理体系的两个公理。

我们前文已经讲到,I组合子可以由KS构造,这对应了希尔伯特公理体系中,命题等价于自身,可以由其它公理推导出来。

以此逻辑构造的逻辑公理体系被称作正蕴含逻辑(positive implicational logic) ,也译作蕴含命题演算

我们亦可知,BCKW和X组合子逻辑,也可以对应到命题逻辑的一组公理,有兴趣可以参考前篇自行推导。

扩展的 Curry-Howard 同构

在数学领域,命题逻辑是多数逻辑系统的基础,若我们为类型系统添加真值和否定逻辑,则可以得到布尔运算逻辑

文字描述 数学符号 类型符号 编程语言(TypeScript)
false ⊥ \bot ⊥ (读作bottom) void
true ⊤ \top ⊤ (读作top) any
非T ¬ T \neg T ¬T T → ⊥ T \rightarrow \bot T→⊥ T => void

为了支持否定逻辑,必须要添加一条公理:
( ¬ X → ¬ Y ) → ( Y → X ) (\neg X \rightarrow \neg Y) \rightarrow (Y \rightarrow X) (¬X→¬Y)→(Y→X)

其对应的基本类型是
( X → ⊥ ) → ( Y → ⊥ ) → ( Y → X ) (X \rightarrow \bot) \rightarrow (Y \rightarrow \bot) \rightarrow (Y \rightarrow X) (X→⊥)→(Y→⊥)→(Y→X)

此公理即所谓的逆否命题公理。我们在初等数学中使用的反证法,就依赖此条公理。在希尔伯特公理体系中,也有此公理。

我们还可以继续根据命题逻辑的上位系统扩展简单类型系统,比如添加谓词和量词逻辑扩展到一阶逻辑 。其对应了 ∑ \sum ∑ 和 ∏ \prod ∏ 类型。但是这样就超出了简单类型论的范围了,包含这两种类型的类型论被称为"依赖类型论",我们留待后文讨论。

结语

基于Curry-Howard同构建立的类型论,与lambda演算逐渐走上了不同的道路,在类型论中,我们更关心lambda表达式本身的结构,而不是对它应用归约求得结果。

从这个时间点开始,类型论的发展与lambda已经实际形成了竞争关系。

对于一个实际问题,我们既可以从类型论的角度把它抽象为"构造lambda表达式成为特定类型",也可以从lambda演算的角度抽象为"计算特定lambda表达式的结果"。

接下来的系列中我们仍然回到lambda演算的发展,更多关于类型论的发展留待其它系列讨论。

相关推荐
云水一下几秒前
TypeScript 从零基础到精通(七):从配置到全栈项目落地
前端·javascript·typescript
十九画生34 分钟前
从同步到异步:重新理解 JavaScript 的执行机制
javascript
半个落月36 分钟前
JavaScript 同步异步与 Promise 详解 —— 从 Event Loop 到手写 sleep
javascript
触底反弹40 分钟前
深入理解 JavaScript 同步与异步:从 Event Loop 到 async/await
javascript
浮生望1 小时前
JavaScript 异步编程核心:从同步阻塞到 Promise 事件循环
javascript·promise
假如让我当三天老蒯1 小时前
暂时性死区是否和闭包是相背的呢(自学用)
前端·javascript
渣波1 小时前
前端开发主页面小技巧
前端·javascript
小林ixn1 小时前
前端必知:JS同步异步与Promise,终于有人讲明白了!
javascript·面试
bonechips1 小时前
JS:同步与异步,从单线程到 Promise 的编程之路
前端·javascript
先吃饱再说1 小时前
为什么 `setTimeout` 会“插队”?JS 事件循环与 Promise 通关笔记
前端·javascript·promise