从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……’”
0x01 不动点
定义函数 𝑓:ℝ↦ℝ,如果 ∃𝑥∈𝐷(𝑓),使得 𝑥=𝑓(𝑥),则称点 𝑥 是函数 𝑓(𝑥) 的不动点.
// 等式 x = f(x) 可以进行如下的无穷变换
x = f(x)
= f(f(x))
= f(f(f(x)))
= ...
0x02 递归
递归的强大之处在于它允许用户用有限的语句描述无限的对象! 瑞士的计算机科学家
尼克劳斯·维尔特
如是说..
考虑一下 阶乘函数 fact
的定义:
fact n = 1 if n == 0 else (n * fact (n-1))
// 尝试进行 lambda 抽象 [忽略表达式的细节]
let zero? = lambda n x y . if-then-else (n == 0) x y
let pred = lambda x . x - 1
fact = lambda n . zero? n 1 (n * fact (pred n))
// 右侧还是有fact,怎么办,再次使用lambda, 把它拽出来试试..
fact = (lambda f . (lambda n . zero? n 1 (n * f (pred n)))) fact
// 看着好乱,抽象一下吧..
F = E(F) // F是fact, E是中间的lambda函数
= E(E(F)) // 可以无穷替换哎!!!
= E(E(E(F)))
= ...
也就是说, F
是函数 E
的不动点,但是这个函数看着好复杂鸭,我该如何求解呢(这里先不讨论高阶函数的求解方法)~
0x03 传说中的 Y组合子
召唤!! Y组合子
!! 一起来瞅瞅这是个啥东东~
Y组合子
作为高级函数不动点的 "通用求根公式"
,只要函数存在不动点,我们就可以通过 Y组合子
结合函数,进行求解
Y(E) = F
= E(F) // F = E(F)
= E(Y(E))
= E(E(Y(E)))
利用 Y组合子
可以求解得到 F
F = Y(E)
= Y(lambda f . (lambda n . zero? n 1 f(n * (pred n))))
那么这个 Y
到底长啥样呢~ (让我们通过一个有趣的小栗子, 一起推导一下吧~)
0x04 推导 Y 组合子[编程]
这节我们将从一个常见的函数出发,编程推导出 Y组合子
,不熟悉编程的小伙伴,可以直接跳过,不过我采用的Lua语言简单灵活,很方便阅读理解的,那么开始吧~
--[[
# Y魔法
# Y Y = Y (Y Y)
]]
local arr1 = {1,2,3,4,5} -- Lua中定义的数组(其实是表,不用在意这些细节)
--[[
这里先定义两个函数,
isEmpty 用于判断数组是否为空;
shift 用于将数组中第一项元素移除, JS玩家应该很熟悉这个命名吧~
]]
local function isEmpty(arr)
if next(arr) == nil then -- 蹩脚的 空表判断 arr == {}, 将就一下吧
return true
end
return false
end
local function shift(arr)
table.remove(arr, 1) -- Lua下标从1开始
return arr
end
-- 好的,开始我们的旅程吧~
--[[
* 现在问题来了,我想求解 数组的长度 该怎么做呢?
]]
-- [务实的小伙伴肯定第一时间想到,Lua有没有内建函数或者语法糖,有的话,那就很方便了鸭~]
-- 一番检索之后,发现 Lua 还真有
-- print(#arr1) -- 哈哈,成功得到了 数组arr 的长度
-- 看来大家都能很轻松地应对嘛~ [就这!??]
-- 那如果,不允许使用内置语法糖呢??
-- [聪明的小伙伴表示: 这也难不倒我~ 看我递归显神通...]
local function rlength(arr)
if isEmpty(arr) then
return 0
end
return 1 + rlength(shift(arr)) -- 使用了本身的函数名
end
-- print(rlength(arr1))
-- (啪-啪-啪~ 鼓掌声)
--[[
但是!! 你们以为这就完了嘛~?~ too young too naive~
[我知道!尾调用优化,改成尾递归形式~]
不不不,不要误会,我不是在一步步刁难各位~
我只是发现,函数体内使用着函数本身的名字,想把它去掉而已
[纳尼!?我看,你就是在刁难我胖虎!!]
]]
local lenX = function(arr)
if isEmpty(arr) then
return 0
end
-- return 1 + ???(shift(arr))
end
--[[
先去看点别的
]]
-- 这个函数做啥鸭,好像不断调用自身,永远不会停止!
local function eternity(x)
return eternity(x)
end
-- 来看看 这个函数是啥
local len0 = function(arr)
if isEmpty(arr) then
return 0
end
return 1 + eternity(shift(arr))
end
-- 我们先跨出最小的一步,求解 长度为0 的数组长度
-- print(len0({}))
-- 那么长度为1 的数组呢
local len1 = function(arr)
if isEmpty(arr) then
return 0
end
return 1 + len0(shift(arr))
end
-- print(len1({1}))
-- 函数展开一下
len1 = function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (arr)
if isEmpty(arr) then
return 0
end
return 1 + eternity(shift(arr))
end)(shift(arr))
end
-- print(len1({1}))
-- 好像 很难没有太多启发嘛,那就继续,看看 长度为2 的数组
local len2 = function (arr)
if isEmpty(arr) then
return 0
end
return 1 + len1(shift(arr))
end
-- print(len2({1,2}))
-- 继续展开
len2 = function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (arr)
if isEmpty(arr) then
return 0
end
return 1 + eternity(shift(arr))
end)(shift(arr))
end)(shift(arr))
end
-- print(len2({1,2}))
-- 开始看着有点绕了呢,但是还是没有看出太多端倪呢~
-- 前进!! 长度为3 的数组
local len3 = function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (arr)
if isEmpty(arr) then
return 0
end
return 1 + eternity(shift(arr))
end)(shift(arr))
end)(shift(arr))
end)(shift(arr))
end
-- print(len3({1,2,3}))
-- 害,重复代码挺多的,看看能不能提取出来
-- 简单一点就从 长度为0 的数组开始吧
len0 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(eternity)
-- print(len0({}))
-- 那么 长度为1,2,3的数组 也就明了了
len1 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(len0)
-- print(len1({1}))
len2 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(len1)
-- print(len2({1,2}))
len3 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(len2)
-- print(len3({1,2,3}))
-- 感觉看不出啥端倪,继续展开看看
len1 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)((function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(eternity))
len2 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)((function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)((function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(eternity)))
len3 = (function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)((function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)((function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)((function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)(eternity))))
-- print(len0({}))
-- print(len1({1}))
-- print(len2({1,2}))
-- print(len3({1,2,3}))
-- 自己观察一下,好像是 f(f(f(ete))) 这样的模式呢,emmmm... 那么再提取试试
len0 = (function (f)
return f(eternity)
end)(function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)
-- print(len0({}))
-- 学废了!! 长度为1,2,3的数组 依样画葫芦~
len1 = (function (f)
return f(f(eternity))
end)(function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)
-- print(len1({1}))
len2 = (function (f)
return f(f(f(eternity)))
end)(function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)
-- print(len2({1,2}))
len3 = (function (f)
return f(f(f(f(eternity))))
end)(function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)
-- print(len3({1,2,3}))
--[[
感觉悟出了一些啥,但是还是没有进一步的思路哎~
害,既然如此,就搞事情!!
eternity函数既然不会执行到, 那就可以随意替换, 比如这样-> [替换自身]
]]
len0 = (function (f)
return f(f)
end)(function (flen)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + flen(shift(arr))
end
end)
-- print(len0({}))
-- emmm~ 心满意足! 再仔细看看,好像flen这个函数名字也可以换一下~ 一家人就是要整整齐齐!~
len0 = (function (f)
return f(f)
end)(function (f)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + f(shift(arr))
end
end)
-- print(len0({}))
-- 规约一下看看是啥样~
len0 = (function (f)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + f(shift(arr))
end
end)(function (f)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + f(shift(arr))
end
end)
-- print(len0({}))
-- 天呐!!! 函数把自己传给了自己~!!!!
-- 进一步规约!
len0 = function (arr)
if isEmpty(arr) then
return 0
end
return 1 + (function (f)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + f(shift(arr))
end
end)(shift(arr))
end
-- print(len0({}))
--[[
细细揣摩一下, F(F) 构造出的函数Z
1. 当传入长度为0的空数组时,返回0 Z({}) => 0
2. Z({1}) => 1 + 函数F(...) 如果此时这个F 还是 F(F) 呢 => F(F)(...)
想象一下,会发生什么~
]]
--
len0 = (function (f)
return f(f)
end)(function (f)
return function (arr)
if isEmpty(arr) then
return 0
end
-- return 1 + f(shift(arr))
return 1 + f(f)(shift(arr)) -- 注意此处的变化
end
end)
-- print(len0({}))
-- print(len0({1,2}))
-- 通过不断产生的 F(F), 不断推迟计算,直至 数组长度为0.
-- 这里是不是蕴涵着某种更通用的神秘力量呢~
-- 进一步提取
lenX = (function (f)
return f(f)
end)(function (f)
return (function (length)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + length(shift(arr))
end
end)
-- (f(f)) -- call-by-value的求值策略下,避免参数无限求值,导致栈溢出
(function (x)
return f(f)(x)
end)
end)
-- print(lenX({1,2,3,4,5}))
-- lenX = (function (f)
-- return f(f)
-- end)((function (flen)
-- return function (f)
-- return flen(function (x)
-- return f(f)(x)
-- end)
-- end
-- end)(function (length)
-- return function (arr)
-- if isEmpty(arr) then
-- return 0
-- end
-- return 1 + length(shift(arr))
-- end
-- end))
-- 再提取
lenX = (function (flen)
return (function (f)
return f(f)
end)(function (f)
return flen(function (x)
return f(f)(x)
end)
end)
end)(function (length)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + length(shift(arr))
end
end)
-- print(lenX({1,2,3,4,5}))
-- OK, 到了这里就差不多了,观察一下函数形式 F = Y(E)
-- 提取Y函数
Y = function (f)
return (function (u)
return u(u)
end)(function (u)
return f(function (x)
return u(u)(x)
end)
end)
end
local final_len = Y(function (length)
return function (arr)
if isEmpty(arr) then
return 0
end
return 1 + length(shift(arr))
end
end)
-- print(final_len({1,2,3,4,5}))
--[[
有了Y函数,我们就能很方便地处理匿名递归
]]
-- 阶乘函数[递归形式]
local function fact(n)
if n == 0 then
return 1
end
return n * fact(n - 1)
end
-- print(fact(4))
-- 阶乘函数[匿名递归]
local anon_fact = Y(function (fact)
return function (n)
if n == 0 then
return 1
end
return n * fact(n - 1)
end
end)
-- print(anon_fact(5))
--[[
[所以这个Y函数到底是啥呢??]
在这里,我们可以称为 -> [应用序]Y组合子
]]
0x05 终于见到 Y
经过上面的编程推演, 最终得到了 Y组合子
.
Y = λf.(λx.(f (x x)) λx.(f (x x))) // Call-By-Name
Y = λf.((λu.(u u)) λx.(f λv.(x x v))) // Call-By-Value
最后用一个例子函数g来展开它,可以看到这个函数是如何成为一个不动点组合子的.
(Y g)
= (λf.(λx.(f (x x)) λx.(f (x x))) g) // 展开
= (λx.(g (x x)) λx.(g (x x))) // λf的β-归约 - 应用主函数于g
= (λy.(g (y y)) λx.(g (x x)))// α-转换 - 重命名约束变量
= (g (λx.(g (x x)) λx.(g (x x)))) // λy的β-归约 - 应用左侧函数于右侧函数
= (g (Y g)) // Y的定义