Y Combinator
前言
我第一次听说Y combinator是在程序语言理论与设计原理这门课程, 同时也是第一次知道lambda演算和图灵机是完全等价的, 但是lambda演算的优雅程度图灵机是完全无法和lambda演算相提并论的。
看懂这篇文章的基础
- 知道什么是高阶函数: 函数可以作为值传递, 也就是常说的lambda表达式
- 知道$$Curry$$这样参数风格:$$\lambda .x \lambda.y.x+y$$
- 知道$$\lambda$$的每一项都是一个约束条件, 约束了参数变量, 否则出现在函数中的变量就是自由的。
- 知道 $$lambda$$演算有$$\beta$$规约和$$\alpha$$等价, 规约就是用$\lambda$项的参数替换掉表达式中相同的名称, $\alpha$等价是1相同的函数具有相同参数, 那么两个lambda项就是等价的($\alpha$等价是发生于没有自由变量的项中)
为什么需要Y combinator?
我们首先不管$\lambda$表达式的一些事情, 先看普通的编程语言中的一个递归实现:
1 | function fact(n) { |
上面是一个在JavaScript中很简单的递归函数, 简单的模拟一下计算过程:
1 | fact(0) = 0; |
那么上面的递归函数和在lambda表达式想表达的递归函数有什么不同吗?
我们是希望lambda表达式中出现的变量都是被约束的, 即不存在自由变量, 而上面的递归函数中是出现了自由变量的, 这个自由变量是fact, 显然它不是$\lambda$这个域(scope)中的, 所以我们是希望把这个自由变量给干掉的, 根据前面讲的每一个$\lambda$项都是一个约束项,所以我们只需要将fact这个变量给放到函数的参数上就可以了,这样做就不会出现自由变量了, 所以就得到下面这段代码:
1 | //curry参数风格 |
那个这个函数fact如何实现递归呢? 答案就是使用一种叫做穷人的Y combinator也就是下面这种形式:
1 | fact(fact)(3) |
但是这种不通用, 我们是否可以使用lambda演算构造出那种无穷递归的函数, 就像下面这种:
1 | //所以你是可以无限进行递归下去的 |
既然这个东西对所有的函数都等价, 那肯定对fact也是等效的, 所以我们只需要规约出Yg定义
推导过程?
下面是完整的推导过程:
1 | //先定义一个函数: |
然后就是改造第二项, 直接将f(f)提出来变成一个参数
1 | (f => (g => n => n == 0 ? 0 : n + g(n-1))(v=>f(f)(v))) |
看到没(g => n => n == 0 ? 0 : n + g(n-1)
就是我们需要的递归定义了, 但是它在里面, 只有把它提到最外边才能得到最终的解
1 | (fa => (f => fa(v=>f(f)(v)))) |
然后带回去:
1 | ff( (fa => (f => fa(v => f(f)(v)))) |
然后再一次的将$\alpha$等价形式提出来:
1 | (dd => ff( (fa => (f => fa(v => f(f)(v))))(dd))) |
然后将ff apply一下进去得到的结果:
1 | (dd => (fa => (f => fa(v => f(f)(v))))(dd) |
然后把dd内联进去, 大工告成, 成功得到Y combinator
1 | (dd => (f => dd(v=>f(f)(v)))(f => dd(v=>f(f)(v)))) |
然后我们可以根据这个Y combinator做非常有意思的事情, 写出任意的纯函数了哈哈
1 | const Y = (dd => (f => dd(v=>f(f)(v)))(f => dd(v=>f(f)(v)))) |