dove
2020-02-01 12:38:52
Church 编码是一种“抽象方法”,它将“数字”、“运算”等概念全数“抽象”成 λ- 演算(别急着跑,会介绍什么是 λ- 演算的),来让程序实现更好的抽象性。换句话说,它将物件(布尔值、自然数、列表、etc)抽象为函数,并通过将公理的基本元素作为参数应用于其上来获得(依基本元素不同而不同的)值。
前置知识:布尔代数,基础 Python,小学数学
- 这篇文章讲的东西,对 OI 有什么用吗?
- 抱歉,几乎没有。如果您不想在 λ- 演算等抽象废话上浪费时间,现在就可以关闭本页面。
- 你讲的真烂/你个初学者还写这种博客班门弄斧。
- 请告诉我如何改进,或者自己写一篇吊打我。
- 我学过 λ- 演算和 Haskell,想看更直接的介绍。
- 可以到这里看这篇文章的原始版本。不过真的有人会了这些还不会 Church encoding 吗...
λ- 演算可以算作最原始的编程语言。我们来通过几个例子了解一下它,每个例子都有对应的 Python 代码(不用 C++ 是因为 C++ 弄这种东西非常困难,不用 Haskell 是因为很多人看不懂)。注意,所有 Python 函数都用 lambda
表达式,这是为了更好地对应,不会的可以学一下。
p1. λ- 演算的函数声明
所有 λ- 演算中的函数都接受且只接受一个参数。来看一个基本的函数:把参数加一。
各个部分的意义:
lambda 符号,相当于“函数声明关键字” | 参数名 | 分隔符 | 函数体 |
---|---|---|---|
Python 里就是
lambda x : x + 1
p2. λ- 演算的函数调用
函数调用没有括号。
f = lambda x : x + 1
a = 3
b = f(a) # => 4.
λ- 演算的执行只有三条公理
p3. λ- 演算的高阶函数
有些人可能有高阶函数的概念。高阶函数是“接受函数作为参数的函数”或者“返回函数的函数”。看一个例子。
g = lambda x : lambda y: x + y
g(1)(2) # 1 + 2 => 3
这种函数一次只接受一个参数,返回等待接受下一个参数的函数,这个函数又返回接受下下个参数的函数...,这种函数叫做“柯里化函数”。
add1 = g(1)
add1(3) # 1 + 3 => 4
在 λ- 演算中也可以直接调用函数表达式
(lambda x : lambda y : x + y)(a)(b) # => a + b
函数接受的参数也可以是函数,比如
(lambda x : lambda y : x(y))(a)(b) # => a(b)
这里的
有时候会见到
\lambda xy. 这样的参数列表,它只是\lambda x. \lambda y. 的简写。
Church 布尔代数通过 Church 编码抽象了标准布尔代数,我们可以通过它来理解 Church 编码。
考虑布尔代数的基本元素,即布尔值:
公理 a1. 布尔值的集合
其中
在 Church 布尔代数中,它们将会如此表示:
定义 a1. Church 布尔代数中的布尔值
可以认为
令
代码 a1.
true = lambda t : lambda f : t
false = lambda t : lambda f : f
接下来考虑在标准布尔代数中的逻辑取反:
公理 a2. 布尔值的取反
不难想到,在 Church 布尔代数中,逻辑取反的定义:
定义 a2. Church 布尔代数中的取反
我们颠倒了逻辑真与逻辑假,这就令原值归约为相反的值。
代码 a2.
cnot = lambda x: lambda t: lambda f: x(f)(t)
公理(定理)a3. 布尔代数中的逻辑与
定义 a3. Church 布尔代数中的逻辑与 有两种理解方式:
如果第一个值为假,那就直接得到结果,否则再判断第二个值是否为假。
将其中一个值(
代码 a3.
cand = lambda x: lambda y: x(y)(x)
公理(定理)a4. 布尔代数中的逻辑或
定义 a4. Church 布尔代数中的逻辑或 有两种理解方式:
如果第一个值为真,那就直接得到结果,否则再判断第二个值是否为真。
将其中一个值(
代码 a4.
cor = lambda x: lambda y: x(x)(y)
公理(定理)a5. 布尔代数中的逻辑异或
定义 a5. Church 布尔代数中的逻辑异或 有两种理解方式:
如果
如果并非
代码 a5.
cxor = lambda x: lambda y: x(cnot(y))(y)
Church Number 编码了自然数及它的运算。
Peano 公理定义了自然数。其他自然数之上的运算与关系都可由其推出。
公理 b1. Peano 公理
2- 元组
并且我们称
可以看到,自然数定义中的基本元素是自然数
定义 b2. 自然数集元素的 Church Number
很容易得到
可以认为
同样地,
代码 b2.
zero = lambda s: lambda z: z
one = lambda s: lambda z: s(z)
two = lambda s: lambda z: s(s(z))
-- ...
自然数加法及证明略。
定义 b3. Church Number 的加法
这里,
代码 b3.
cadd = lambda x. lambda y. lambda s. lambda z. x(s)(y(s)(z))
自然数乘法及证明略。
定义 b4. Church Number 的乘法
这里,
代码 b4.
cmul = lambda x. lambda y. lambda s. x(y(s))
(这一部分用 Python 实在难讲,就用 Haskell 了,能看的凑合看看吧)
自然数幂及证明略。
回顾一下,Church Number 的类型是:
type Cnum a = (a -> a) -> a -> a
-- Successor -> Zero -> Number
(->)
是右结合的,加对括号也无妨:
type Cnum a = (a -> a) -> (a -> a)
这说明了,Church Number 可以看作“接受一个函数,并返回该函数的若干次幂的高阶函数”。这里“幂”的“乘法”是 function compose,即 Haskell 中的 (.)
,范畴论中的
Church Number 的幂是这样的:
定义 b5. Church Number 的幂
其中
可以想见,此函数的类型需要是这样才能工作:
代码 b5.
cexp :: Cnum a -> (Cnum a -> Cnum a) -> Cnum a
cexp x e = e x
第一眼看上去很难懂,我们回到刚才的话题:
Church Number 可以看作接受一个函数,并返回该函数的若干次幂的高阶函数。
再看一下那个指数的类型:
e :: Cnum a -> Cnum a
{- 展开为 -} e :: ((a -> a) -> (a -> a)) -> ((a -> a) -> (a -> a))
{- 也就是 -} e :: Cnum (a -> a)
也就是说,这个指数是一个“高阶 Church Number”,也是一个“函数的 Church Number”。
理解了这个概念,我们再回来看 Church Number 的幂。举一个例子,
其中
归约一下看看,就会发现我们的思路是正确的: