functional

functional

Not enough ratings
如何发现 Y 组合子
By Paandaa
我能想明白的一种 Y 组合子(游戏第 4 章“递归”第 1 题)的发现过程。

没其他地方可发,所以发到 Steam 指南里了。
   
Award
Favorite
Favorited
Unfavorite
记号
本指南使用与游戏中略有不同的 λ 演算语法:将游戏中的“变量 :”替换为“λ 变量 .”。

1、2、3、ZERO、MUL、PRE 等记号与游戏第 5 章“数字”引入的同名记号含义相同。

记号“:=”表示将其右侧的表达式作为左侧记号的定义;“=>”表示其左侧的表达式可规约为其右侧的表达式。
动机
仿照一般编程语言,可以写出“递归”的阶乘函数:
FACTO_1 := λn. ZERO n 1 (MUL n (FACTO_1 (PRE n)))
但 λ 演算中实际上并没有声明、定义一个符号的操作,诸如 ZERO、1 等记号只是一种类似 C 语言中宏的语法糖,所以无法这样实现递归函数。

为了在函数中调用自己,它需要有个名字。我们可以给它增加一个参数表示函数本身,这样就可以用参数名代替函数名,递归调用自己了:
FACTO_2 := λf. // 新增一个参数 f 表示函数本身 λn. ZERO n 1 (MUL n ( f f // 因为新增了参数,所以调用时也要传递进去 (PRE n) ))
调用该函数时,要像这样:
FACTO_2 FACTO_2 2

但是这太丑了,在调用时要写两遍函数名,函数体内也要写两个 f。但是,理想中的写法
FACTO := λf. λn. ZERO n 1 (MUL n ( f // 只写一个 f (PRE n) ))
传入参数的个数不正确,根本是算不出预期结果的。由此想到,是否可以用某个高阶函数 Y “修饰”一下 FACTO,让 Y FACTO 变成一个递归函数呢?
发现 Y 组合子
在之前定义的函数 FACTO_2 中,通过将函数自身传递给自身作为参数,实现了递归的功能。FACTO 与 FACTO_2 的唯一不同,即是将定义中的两个 f 替换成了一个 f。利用高阶函数 Y,将传递给 FACTO 的第一个参数 f 变成两个 FACTO,能否正确地实现递归呢?令
Y_0 := λf. f (f f)
计算 3 的阶乘:
Y_0 FACTO 3 => FACTO (FACTO FACTO) 3 // 在这里调用形式是 FACTO (FACTO FACTO) n => MUL 3 (FACTO FACTO 2) // 在这里调用形式是 FACTO FACTO n => MUL 3 (MUL 2 (FACTO 1)) // 在这里调用形式是 FACTO n => 错误
从中发现,随着“递归”的进行,FACTO 的第一个参数 f 变得越来越“短”:只要 f 是有限个 FACTO,这样的“递归”总会在充分大的次数后停止。因此,f 应该能够自我复制,这样无论进行多少次规约,总还有新的 f 被传入 FACTO。

考虑函数
t := λx. x x

t t => (λx. x x) t => t t => ...
t 具有自己复制自己的功能。仿照这个例子,要让 f 能够自我复制,它应该像这样被调用:
f f => FACTO (f f)
因为 f 与 FACTO 有关,所以 f 是某个高阶函数 g 对 FACTO 进行修饰后的结果,也就是 f = g FACTO。将上式的 f 做替换可得:
g FACTO (g FACTO) => FACTO (g FACTO (g FACTO))
写到这里,实际上我们已经找到了 g。如果你没有注意到,直接把 g 的定义写出来就非常显然了:
g := λr. λx. r (x x)

根据 g 的定义,我们可以写出 Y,一个接受“递归”函数 r 做参数的高阶函数。Y 的作用是将我们找到的自复制函数 f f,也就是 g r (g r) 作为参数传递给 r:
Y := λr. r (g r (g r))
利用 g 的定义,可以写出等价的定义:
Y := λr. g r (g r)
再对这个式子中的两个 g r 各规约一步,即可得到我们最熟知的 Y 组合子的形式:
Y := λr. g r (g r) => λr. (λx. r (x x)) (λx. r (x x)) => λf. (λx. f (x x)) (λx. f (x x))
最后
感谢这款游戏,让我读了一些文章,并将以上能说服我自己的 Y 组合子发现过程整理下来。

但我对这款游戏的评价不变。如果不是游戏对 Y 组合子的引入如此突兀,为什么会有这篇指南呢。