lambda 演算的基本语法只有变量、函数和调用。前一篇已经写出规约器,说明函数应用可以通过替换一步步化简。

接下来的问题是数据从哪里来。日常编程语言有数字、布尔值、条件表达式和集合;纯 lambda 演算没有这些原语。Church 编码给出一种极端答案:数据可以表示成行为。布尔值是“选择左边还是右边”的函数,数字是“把某个函数重复执行多少次”的函数。

本文用 Python 高阶函数实现 Church boolean 和 Church numeral,再把它们映射回工程直觉。

数据也可以是行为

普通编程里,数字 2 看起来是一块数据。Church 编码换一个定义:

1
2 = 接收函数 f 和初始值 x,然后执行 f(f(x))

也就是说,数字不再保存为整数对象,而是保存为“重复应用函数的次数”。

布尔值也类似:

1
2
true  = 选择第一个分支
false = 选择第二个分支

这个视角把“值是什么”改成“值能做什么”。在 lambda 演算里,只要函数和调用足够表达这些行为,就可以构造出数字、布尔值和条件选择。

Church 布尔值

Church boolean 的定义很短。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections.abc import Callable
from typing import TypeVar


T = TypeVar("T")
ChurchBoolean = Callable[[T], Callable[[T], T]]


def TRUE(x: T) -> Callable[[T], T]:
return lambda y: x


def FALSE(x: T) -> Callable[[T], T]:
return lambda y: y

TRUE 接收两个值,返回第一个。FALSE 接收两个值,返回第二个。条件表达式可以直接写成:

1
2
def IF(condition: ChurchBoolean[T]) -> Callable[[T], Callable[[T], T]]:
return lambda consequence: lambda alternative: condition(consequence)(alternative)

IF(TRUE)("left")("right") 得到 "left"IF(FALSE)("left")("right") 得到 "right"。条件判断不需要特殊语法,只是把布尔函数应用到两个候选分支上。

模式提炼:布尔值是选择器

1
boolean = choose(first, second)

这个模式在工程中并不陌生。策略对象、回调、分支选择器都可以把“状态值”改写成“选择行为”。布尔值不必总是先变成 if,也可以作为一个可调用对象决定后续路径。

Church 数字

Church numeral 把自然数定义成函数重复次数。

1
2
3
4
5
ChurchNumeral = Callable[[Callable[[T], T]], Callable[[T], T]]


def ZERO(f: Callable[[T], T]) -> Callable[[T], T]:
return lambda x: x

ZERO 表示执行 f 零次,所以直接返回初始值 x

自增函数把一个数字 n 变成 n + 1

1
2
def INCREMENT(n: ChurchNumeral[T]) -> ChurchNumeral[T]:
return lambda f: lambda x: f(n(f)(x))

n(f)(x) 表示执行 fn 次。外层再包一层 f(...),就变成执行 n + 1 次。

于是可以构造:

1
2
3
ONE = INCREMENT(ZERO)
TWO = INCREMENT(ONE)
THREE = INCREMENT(TWO)

用 Church 数字构造数字

Church 数字也可以“驱动”另一个函数。TWO(INCREMENT)(ZERO) 的含义是:从 ZERO 开始,把 INCREMENT 执行两次。

1
2
3
TWO(INCREMENT)(ZERO)
= INCREMENT(INCREMENT(ZERO))
= TWO

这就是“数据作为行为”的味道。TWO 本身不保存整数 2,它保存的是“执行两次”的能力。把这个能力作用在 INCREMENTZERO 上,就得到 Church 数字二。

加法也可以用同样方式实现。

1
2
def ADD(m: ChurchNumeral[T]) -> Callable[[ChurchNumeral[T]], ChurchNumeral[T]]:
return lambda n: lambda f: lambda x: m(f)(n(f)(x))

n(f)(x) 先执行 fn 次,m(f)(...) 再执行 m 次,总共就是 m + n 次。

转回 Python 值观察结果

为了让输出可读,可以写两个转换函数。

1
2
3
4
5
6
def to_bool(boolean: ChurchBoolean[bool]) -> bool:
return boolean(True)(False)


def to_int(numeral: ChurchNumeral[int]) -> int:
return numeral(lambda x: x + 1)(0)

to_bool 把 Church boolean 应用到 Python 的 TrueFalseto_int 把 Church numeral 应用到 “加一” 函数和初始值 0,观察它执行了几次。

完整示例

完整代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from collections.abc import Callable
from typing import TypeVar


T = TypeVar("T")
ChurchBoolean = Callable[[T], Callable[[T], T]]
ChurchNumeral = Callable[[Callable[[T], T]], Callable[[T], T]]


def TRUE(x: T) -> Callable[[T], T]:
return lambda y: x


def FALSE(x: T) -> Callable[[T], T]:
return lambda y: y


def IF(condition: ChurchBoolean[T]) -> Callable[[T], Callable[[T], T]]:
return lambda consequence: lambda alternative: condition(consequence)(alternative)


def ZERO(f: Callable[[T], T]) -> Callable[[T], T]:
return lambda x: x


def INCREMENT(n: ChurchNumeral[T]) -> ChurchNumeral[T]:
return lambda f: lambda x: f(n(f)(x))


def ADD(m: ChurchNumeral[T]) -> Callable[[ChurchNumeral[T]], ChurchNumeral[T]]:
return lambda n: lambda f: lambda x: m(f)(n(f)(x))


def to_bool(boolean: ChurchBoolean[bool]) -> bool:
return boolean(True)(False)


def to_int(numeral: ChurchNumeral[int]) -> int:
return numeral(lambda x: x + 1)(0)


ONE = INCREMENT(ZERO)
TWO = INCREMENT(ONE)
THREE = INCREMENT(TWO)
TWO_BY_ITERATION = TWO(INCREMENT)(ZERO)
FIVE = ADD(TWO)(THREE)

print("true:", to_bool(TRUE))
print("false:", to_bool(FALSE))
print("if true:", IF(TRUE)("left")("right"))
print("if false:", IF(FALSE)("left")("right"))
print("zero:", to_int(ZERO))
print("one:", to_int(ONE))
print("two:", to_int(TWO))
print("three:", to_int(THREE))
print("two by iteration:", to_int(TWO_BY_ITERATION))
print("two + three:", to_int(FIVE))

运行结果:

1
2
3
4
5
6
7
8
9
10
true: True
false: False
if true: left
if false: right
zero: 0
one: 1
two: 2
three: 3
two by iteration: 2
two + three: 5

读取执行结果

前四行说明 Church boolean 只做选择:

1
2
3
4
true: True
false: False
if true: left
if false: right

TRUEFALSE 的差别只在于返回第一个参数还是第二个参数。IF 没有实现新的控制结构,只是把条件函数应用到两个分支上。

后六行说明 Church numeral 是重复次数:

1
2
3
4
5
6
zero: 0
one: 1
two: 2
three: 3
two by iteration: 2
two + three: 5

to_int 用 Python 的加一函数观察重复次数。TWO_BY_ITERATION 展示了 Church 数字可以驱动 INCREMENTFIVE 展示了加法可以由函数组合得到。

Java 程序员的迁移表

Church 编码 Java 对照 工程含义
TRUE / FALSE Function<T, Function<T, T>> 分支选择器
IF(condition) 策略选择 / lazy branch 控制结构函数化
ZERO / ONE / TWO 高阶函数 数据表示为调用次数
INCREMENT 函数组合 在行为外再包一层行为
ADD 组合器 把两个行为串接
to_int adapter 把抽象表示投影成宿主语言值

Java 里直接写 Church 编码会很别扭,因为泛型和函数式接口语法较重。但它提供了一个清晰的抽象:数据结构不一定靠字段保存,也可以靠统一接口暴露行为。策略模式、访问者模式、解释器模式都能看到这个思路。

模式提炼:数据表示的抽象层

1
external_value <-> encoded_behavior

to_intto_bool 很像适配器。内部使用 Church 编码,外部观察时再转回 Python 值。工程系统里也常见这种分层。

场景 内部表示 外部观察
Church numeral 函数重复次数 Python int
ORM 对象图 / change set SQL
编译器 IR 中间表示 机器码 / 字节码
查询引擎 逻辑计划 结果集
规则引擎 规则网络 决策结果

这一层抽象让内部表示可以按计算需求设计,而不被外部展示形式绑死。

与 lambda 规约器的关系

本文使用 Python 函数直接运行 Church 编码。放回前一篇的 lambda 规约器里,TRUEFALSEZEROINCREMENT 也可以写成 lambda 表达式,然后通过规约得到同样结果。

差别只在表现形式:Python 函数把规约交给宿主语言执行,lambda 规约器显式打印每一步替换。两者表达的是同一件事:函数应用足以表示数据和计算过程。

练习

第一,实现 MULTIPLY(m)(n),让它表示 m * n

第二,实现 IS_ZERO(n),让 IS_ZERO(ZERO) 得到 TRUEIS_ZERO(ONE) 得到 FALSE

第三,把 to_int 的初始值从 0 改成 10,观察 Church numeral 表示的“次数”如何作用在不同初始值上。

第四,用 Java 的 Function 尝试写出 TRUEFALSE,比较类型声明的复杂度。

参考资料