Church 编码学习笔记
简介
既然 \(\lambda\) 演算与图灵机等价,那么理论上图灵机能干的事情都能通过 \(\lambda\) 演算表示。
考虑一个不支持任何除了 \(\lambda\) 表达式的编程语言…… 假设我们要在其中建立一个支持计数,并且支持各种运算的表示系统,该如何只用 \(\lambda\) 表达式来构造它?更进一步,是否可以仅用 \(\lambda\) 表达式来构造诸如链表的数据结构?
Church 编码便能解决这一问题,其可以定义与布尔数等价的 Church Boolean,借助皮亚诺公理 (Peano Axioms) 能够定义出与自然数等价的 Church Numeral,甚至还可以定义出 Church Pairs 和 Church List…… 本文作为自己在学习 Church 编码所记录的笔记,可能有很多不合适的地方,等到以后自己有更加深入的理解后再来修改吧……
本文会随自己学习精度慢慢(咕咕)更新……
Church Booleans
定义
首先来考虑构造真和假。既然有两样东西,考虑选择二元函数:对于真所代表的函数,它总是返回第一个参数;而对于否所代表的函数,它总是返回第二个参数。于是:
\[ \begin{aligned} true & = \lambda x \ . \ \lambda y \ . \ x \\ false & = \lambda x \ . \ \lambda y \ . \ y \end{aligned} \]
发现这样构造带来一个非常有趣的性质:Church Boolean 似乎内置了 \(\text{if - then - else}\) 这种表达形式。考虑如下 \(\lambda\) 表达式:
\[ b \enspace T \enspace F \]
其中 \(b\) 是一个 Church Boolean。发现其实际含义就是,若 \(b\) 为真,则结果为 \(T\),否则结果为 \(F\),跟 \(\text{if } \ b \ \text{ then } \ T \ \text{ else } \ F\) 实际上没有什么区别。所以实际上可以定义自己的条件分支表达式:
\[ iff = \lambda b \ . \ \lambda t \ . \ \lambda f \ . \ b \enspace t \enspace f \]
运算
可以应用上面提到的性质比较轻松地定义出所有的布尔逻辑运算……
非运算
先来看最简单的非运算。非运算是一个单参数的函数,对于 Church Boolean \(b\) 可以表示成 \(\text{ if } \ b \ \text{ then } \ true \ \text{else} \ false\),因此可以写作:
\[ not = \lambda b \ . \ false \enspace true \]
与运算
与运算则是一个双参数的函数,当且仅当两参数 \(a, b\) 都为 \(true\) 是才会返回 \(true\),否则返回 \(false\)。可以表示成 \(\text{ if } \ a \ \text{ then } ( \ \text{ if } \ b \ \text{ then } \ true \ \text{ else } \ false \ ) \ \text { else } \ false\),因此:
\[ and = \lambda a \ . \ \lambda b \ . \ a \enspace (b \enspace true \enspace false) \enspace false \]
或运算
或运算也是一个双参数的函数,只要两参数 \(a, b\) 中存在一个 \(true\) 即返回 \(true\),否则返回 \(false\)。可以表示成 \(\text{ if } \ a \ \text{ then } \ true \ \text{else} \ ( \ \text{ if } \ b \ \text{ then } \ true \ \text{ else } \ false \ )\),因此:
\[ or = \lambda a \ . \ \lambda b \ . \ a \enspace true \enspace (b \enspace true \enspace false) \]
其他?
诸如异或、同或等运算已经可以由上述三种运算组合起来表示了。
实现
用 Haskell 实现( CodeWars: Church Booleans):
{-# Language RankNTypes #-}
import Prelude hiding (Bool, False, True, not, and, or, (&&), (||), (==), (/=))
type Boolean = forall a. a -> a -> a
true :: Boolean
false,= \ t f -> f
false = \ t f -> t
true
not :: Boolean -> Boolean
and, or, xor :: Boolean -> Boolean -> Boolean
not = \ a -> a false true
and = \ a b -> a (b true false) false
or = \ a b -> a true (b true false)
Church Maybe
Church Numerals
Peano Axioms
在讨论 Church 数前,先来看看自然数是怎么被定义的。显然可以递归地定义自然数(类似于数学归纳法),首先定义 \(0\),其次对于任意自然数 \(n\) 定义一个能获得其后继元素的函数 \(S(n)\)(对于自然数而言,显然 \(S(n) = n + 1\))就行了。严谨地说,定义自然数集 \(\mathbb{N}\) 的过程如下:
- \(0 \in \mathbb{N}\)
- 定义等价关系 (equality relation):
- 自反性:\(\forall x \in \mathbb{N},\ x = x\)
- 对称性:\(\forall x, y \in \mathbb{N},\ x = y \Rightarrow y = x\)
- 传递性:\(\forall x, y, z \in \mathbb{N},\ x = y,\ y = z \Rightarrow x = z\)
- 封闭性:\(\forall x \in \mathbb{N},\ x = y \Rightarrow y \in \mathbb{N}\)
- 定义算术性质 (arithmetical properties):
- \(\forall n \in \mathbb{N},\ S(n) \in \mathbb{N}\)
- \(\forall m, n \in \mathbb{N},\ m = n \Leftrightarrow S(m) = S(n)\)
- \(\forall n \in \mathbb{N},\ S(n) \neq 0\)
- 归纳公理 (induction axiom):
- 若 \(\mathbb{K}\) 是一个集合,满足:
- \(0 \in \mathbb{K}\)
- \(\forall n \in \mathbb{K},\ S(n) \in \mathbb{K}\)
- 则:\(\mathbb{N} \subseteq \mathbb{K}\)
- 若 \(\mathbb{K}\) 是一个集合,满足:
定义
既然只要有了后继函数和 \(0\) 就可以表示出所有的自然数,那么依然考虑用二元函数来表述 Church 数,形如:
\[ \lambda f \ . \ \lambda x \ . \ something \]
其中第一个参数 \(f\) 可以看作后继函数,第二个参数 \(x\) 可看作 \(0\) 。由此可以很方便地使用这个式子定义 \(0\) :
\[ C_0 = \lambda f \ . \ \lambda x \ . \ x \]
可以看到,不论我们传递给它什么后继函数,它所返回的结果都是 \(0\) 。进一步,也可以比较轻松地定义:
\[ \begin{aligned} C_1 & = \lambda f \ . \ \lambda x \ . \ f \enspace x \\ C_2 & = \lambda f \ . \ \lambda x \ . \ f \enspace (f \enspace x) \\ C_3 & = \lambda f \ . \ \lambda x \ . \ f \enspace (f \enspace (f \enspace x)) \\ & \dots \end{aligned} \]
可见,我们可以通过 Church Numeral 得到与自然数等价的任何事物。例如对于自然数 \(n\),有:
\[ n = C_n \enspace (+1) \enspace 0 \]
基础运算
后继元素
在尝试解决加法前我们先解决一个看起来简单一些的问题:以 \(C_n\) 为参数,而以 \(C_{n + 1}\) 为返回值的函数 \(succ\) 应当如何定义?
如果我们能够定义出 \(succ\),那么将 \(succ\) 作用 \(b\) 次于 \(C_a\) 即可达到 \(C_a + C_b\) 的效果,更进一步,如果定义出了加法,则将 \((+ C_a)\) 作用 \(b\) 次于 \(0\) 即可达到 \(C_a \cdot C_b\) 的效果。
为了方便理解,先继续以自然数 \(n\) 作为例子:
\[ \begin{aligned} n + 1 & = C_{n + 1} \enspace (+1) \enspace 0 \\ & = C_n \enspace (+1) \enspace 1 \\ & = C_n \enspace (+1) \enspace (C_1 \enspace (+1) \enspace 0) \\ \end{aligned} \]
这启示我们,当 \(succ\) 作用于 \(C_n\) 时,只需要保持后继函数 \(f\) 不变,同时将零元由 \(x\) 变为 \(f \enspace x\) 就可以了。因此:
\[ succ = \lambda M \ . \ \lambda f \ . \ \lambda x \ . \ f \enspace (M \enspace f \enspace x) \]
举个例子,试试用它作用于 \(C_0\):
\[ \begin{aligned} succ \enspace C_0 & = (\lambda M \ . \ \lambda f \ . \ \lambda x \ . \ f \enspace (M \enspace f \enspace x)) \enspace \lambda f \ . \ \lambda x \ . \ x \\ & = (\lambda f \ . \ \lambda x \ . \ f \enspace (M \enspace f \enspace x)) \enspace [M := \lambda f \ . \ \lambda x \ . \ x] \\ & = (\lambda g \ . \ \lambda y \ . \ g \enspace (M \enspace g \enspace y)) \enspace [M := \lambda f \ . \ \lambda x \ . \ x] \\ & = (\lambda g \ . \ \lambda y \ . \ g \enspace ((\lambda f \ . \ \lambda x \ . \ x) \enspace g \enspace y)) \\ & = \lambda g \ . \ \lambda y \ . \ g \enspace y \\ & = \lambda f \ . \ \lambda x \ . \ f \enspace x \\ & = C_1 \end{aligned} \]
加法
前面成功定义出了 \(succ\),但还留下了一个问题:怎么表示 \(\underbrace{succ \circ succ \circ \dots \circ succ}_{b \text{ times}}\)(下面简写为 \(succ^b\))?
\[ \begin{aligned} succ^b & = [ \lambda M \ . \ \lambda f \ . \ \lambda x \ . \ f \enspace (M \enspace f \enspace x) ]^b \\ & = \lambda x \ . \ f^b \enspace x \enspace [ f := \lambda M \ . \ \lambda f \ . \ \lambda x \ . \ f \enspace (M \enspace f \enspace x) ] \\ & = (\lambda f \ . \ \lambda x \ . \ f^b \enspace x) \enspace \lambda M \ . \ \lambda f \ . \ \lambda x \ . \ f \enspace (M \enspace f \enspace x) \\ & = C_b \enspace succ \end{aligned} \]
简要推广,对于任意一元 Church Numeral 函数 \(f\),都有:
\[ f^b = C_b \enspace f \]
于是对于加法:
\[ \begin{aligned} C_a + C_b & = (+ C_b) \enspace C_a \\ & = (C_b \enspace succ) \enspace C_a \end{aligned} \]
我们也可以不考虑 \(succ\),换一个角度考虑加法:其可看作二元函数,输入 \(C_a, C_b\) 并且返回 \(C_{a + b}\)。依然以自然数 \(a, b\) 举例:
\[ \begin{aligned} a + b & = C_{a + b} \enspace (+1) \enspace 0 \\ & = C_a \enspace (+1) \enspace b \\ & = C_a \enspace (+1) \enspace (C_b \enspace (+1) \enspace 0) \end{aligned} \]
故将 \(C_a\) 作用于后继函数 \(f\),同时将 \(C_b\) 作用于零元 \(x\) 即可得到 \(C_{a + b}\):
\[ (+) = \lambda M \ . \ \lambda N \ . \ \lambda f \ . \ \lambda x \ . \ M \enspace f \enspace (N \enspace f \enspace x) \]
乘法
与之前类似地,可以借助加法表示乘法,将 \((+ C_a)\) 作用 \(b\) 次于 \(C_0\) 即可:
\[ C_a \cdot C_b = C_b \enspace (+ C_a) \enspace C_0 \]
若不借助加法,可考虑:乘法运算以 \(C_a, C_b\) 为参数,并返回 \(C_{a \cdot b}\)。依然以自然数 \(a, b\) 举例:
\[ \begin{aligned} a \cdot b & = C_{a \cdot b} \enspace (+1) \enspace 0 \\ & = C_a \enspace (+b) \enspace 0 \\ & = C_a \enspace (C_b \enspace (+1)) \enspace 0 \end{aligned} \]
这启示我们,只需要将 \(C_a, C_b\) 依次作用于后继函数 \(f\),并且保持零元 \(x\) 不变即可:
\[ (\cdot) = \lambda M \ . \ \lambda N \ . \ \lambda f \ . \ \lambda x \ . \ M \enspace (N \enspace f) \enspace x \]
幂运算
与自然数的情况类似,幂运算还是得借助乘法实现,将 \((\cdot C_a)\) 作用 \(b\) 次于 \(C_1\) 即可:
\[ C_a \hat{\enspace} C_b = C_b \enspace (\cdot C_a) \enspace C_1 \]
实现
用 Haskell 实现( CodeWars: Church Numbers):
{-# LANGUAGE RankNTypes #-}
import Prelude hiding (succ)
newtype Number = Nr (forall a. (a -> a) -> a -> a)
zero :: Number
= Nr (\ _ x -> x)
zero
succ :: Number -> Number
succ (Nr a) = Nr (\ f x -> f (a f x))
one :: Number
= succ zero
one
add :: Number -> Number -> Number
Nr a) = a succ
add (-- add (Nr a) (Nr b) = Nr (\ f x -> a f (b f x))
mult :: Number -> Number -> Number
Nr a) b = a (add b) zero
mult (-- mult (Nr a) (Nr b) = Nr (\ f x -> a (b f) x)
pow :: Number -> Number -> Number
Nr n) = n (mult x) one pow x (
减法?
对于 Church Numeral 的减法是一个非常有趣且不简单的问题。先考虑较简单的情况:对于以 \(C_n\) 为参数,\(C_{n - 1}\) 为结果的函数 \(pred\) 应当如何定义(即求前驱元素)。
很快发现一个问题:\(pred \enspace C_0 = ?\) 为了保证 \(pred\) 是 well-defined 的,则:
\[ pred \enspace C_n = \begin{cases} C_0 & n = 0 \\ C_{n - 1} & \text{otherwise} \end{cases} \]