推导Y组合子
这篇文章的创作动机是:之前一直不理解Y组合子是怎么被想出来的,查这方面的资料看到了重新发明 Y 组合子 JavaScript(ES6) 版,但是写得不太清楚,于是决定在本文彻底讲清楚。
本文的主要内容是在递归函数的非递归定义中引入omega组合子和Y组合子,并推导他们的形式。本文分为三个部分:
- 在FP中通常用递归的方式定义阶乘函数,本文首先对其给出了一个非递归的定义。
- 对上述过程,提出递归函数非递归定义的一般方法,并在此过程中提出omega组合子。
- 对这种一般方法进行化简,提出Y组合子。
所有的lambda表达式都是用Haskell语法书写的。有些地方为了方便,表达式的书写风格不太好,但是保证读者可以看懂。
文章目录
阶乘函数的非递归定义
首先对阶乘函数fac
给出一个递归的定义:
-- function cond: expresses "select by condition" in a functional form
cond True x y = x
cond False x y = y
-- function fac: the impure version of the factorial function's definition
fac = \x -> cond (x==0) 1 (x * (fac (x-1)))
fac
是递归定义的,右侧表达式中引用了自身:即fac = ...fac...
。
在这一节,我们希望为阶乘函数给出一个非递归的定义,用一个不包含name的lambda expression来表示阶乘函数。
构造h
定义h
满足h h = fac
,则有
h h = \x -> cond (x==0) 1 (x * (h h (x-1)))
h = \h -> \x -> cond (x==0) 1 (x * (h h (x-1)))
注意到此时的h已经是一个纯的lambda expression,因为可以换名
h = \f -> \x -> cond (x==0) 1 (x * (f f (x-1)))
也就是说,我们最终定义了一个h函数,这个函数满足h h = fac
,而且是用一个纯的lambda expression定义的(非递归定义的)。
-- function h: defined based on fac
h = \f -> \x -> cond (x==0) 1 (x * (f f (x-1)))
-- satisfies: h h = fac
构造h
可能是本文唯一一处不自然的东西。为什么要这么做?这一点我暂时也没想明白。
阶乘函数的非递归定义
我们抛弃原来fac
的定义,重新定义fac = h h
,其中h = \f -> \x -> cond (x==0) 1 (x * (f f (x-1)))
. 这样我们就对fac
给出了非递归的定义。
用归纳法证明forall n >= 0, fac n = h h n = n!
:
pre:
-- fac = (\x -> cond (x==0) 1 (x * (h h (x-1))))
fac
= h h -- unfold fac
= (\f -> \x -> cond (x==0) 1 (x * (f f (x-1)))) h -- unfold the 1st h
= (\x -> cond (x==0) 1 (x * (h h (x-1)))) -- assign the 2nd h to f
-- similarly, h h = (\x -> cond (x==0) 1 (x * (h h (x-1))))
for n = 0:
-- fac 0 = 1
fac 0
= (\x -> cond (x==0) 1 (x * (h h (x-1)))) 0 -- due to pre
= 1 -- assign 0 to x
-- similarly, h h 0 = 1
forall n >= 1, if fac (n-1) = h h (n-1) = (n-1)!, then
-- fac n = n!
fac n
= (\x -> cond (x==0) 1 (x * (h h (x-1)))) n -- due to pre
= n * (h h (n-1)) -- assign n to x
= n * (n-1)! -- due to the hypo
= n! -- evaluation of *
-- similarly, h h n = n!
综上所述,我们给出了fac
的非递归定义:
h = \f -> \x -> cond (x==0) 1 (x * (f f (x-1)))
fac = h h
omega组合子和不动点
上一节给出了阶乘函数的非递归定义。本节指出,上一节的推导存在一般性,可以推广到所有一阶的递归函数。
本节首先对上一节的定义进行了一些等价变换,从而提出了omega组合子和不动点两个概念,然后给出了非递归定义递归函数的一般方法。
omega组合子
fac
的非递归定义:
h = \f -> \x -> cond (x==0) 1 (x * (f f (x-1)))
fac = h h
我们首先的目的是化简这个定义。观察到这个定义中出现了相同的模式:f f
和h h
,可以定义一个函数来总结这种模式:
-- function w: omega combinator
w = \f -> f f
这个用字母w
表示的函数称为omega组合子。有了这个函数,上面的定义可以被优化成:
w = \f -> f f
h = \f -> \x -> cond (x==0) 1 (x * (w f (x-1)))
fac = w h
omega组合子具有一些其他奇妙的性质,比如w w = w w
不动点
我们进一步尝试优化h
函数。回归原始,首先审视fac
函数的非递归定义。
fac = \x -> cond (x==0) 1 (x * (fac (x-1)))
对上式进行beta reduction,把fac
函数本身变成一个可以输入的参数:
fac = \x -> cond (x==0) 1 (x * (fac (x-1)))
fac = (\fac -> \x -> cond (x==0) 1 (x * (fac (x-1)))) fac
fac = (\f -> \x -> cond (x==0) 1 (x * (f (x-1)))) fac
我们发现了fac'
函数:
-- function fac' is defined based on the impure definition of function fac
fac' = \f -> \x -> cond (x==0) 1 (x * (f (x-1)))
-- satisfies: fac' fac = fac
这个函数的性质是fac' fac = fac
。在这种情况,称fac
是fac'
的不动点,就像数学里对不动点的定义一样。
对比fac'
函数和h
函数,注意到forall f, fac' (w f) = h f
,所以h = fac' . w
,进而我们可以把阶乘函数的定义修改如下:
w = \f -> f f
fac' = \f -> \x -> cond (x==0) 1 (x * (f (x-1)))
h = fac' . w
fac = w h
也就是:
w = \f -> f f
fac' = \f -> \x -> cond (x==0) 1 (x * (f (x-1)))
fac = w (fac' . w)
证明正确性:归纳法证明fac n = ((fac' . w) (fac' . w)) n = n!
pre:
-- fac = fac' ((fac' . w) (fac' . w))
fac
= w (fac' . w) -- definition
= (fac' . w) (fac' . w) -- apply the 1st w
= fac' (w (fac' . w)) -- apply the 1st .
= fac' ((fac' . w) (fac' . w)) -- apply the 1st w
-- similarly, (fac' . w) (fac' . w) = fac' ((fac' . w) (fac' . w))
for n = 0:
-- fac 0 = 1
fac 0
= fac' ((fac' . w) (fac' . w)) 0 -- due to pre
= 1 -- apply the 1st fac'
-- similarly, ((fac' . w) (fac' . w)) 0 = 1
forall n >= 1, if fac (n-1) = ((fac' . w) (fac' . w)) (n-1) = (n-1)!, then
fac n
= fac' ((fac' . w) (fac' . w)) n -- due to pre
= n * (((fac' . w) (fac' . w)) (n-1)) -- apply the 1st fac'
= n * (n-1)! -- due to the hypo
= n! -- evaluation of *
递归函数的非递归定义:一般方法
结合本节以上内容,我们已经总结出了非递归定义递归函数的一般方法,以递归函数fac
为例:
-
一开始,递归函数的定义是这样的:
fac = ...fac...
-
我们根据递归函数的具体形式,提出这样的非递归函数:
fac' = \fac -> ...fac...
,此时这个函数满足fac' fac = fac
-
然后借助omega组合子,用非递归函数重新定义递归函数:
fac = w (fac' . w)
对于这种一阶的递归函数(即定义中只因引用自身导致递归的函数),可以用以上的通式来给出纯lambda expression的定义。
提出Y组合子
本节主要是继续上一节的工作,引出Y组合子,并基于Y组合子给出更好的递归函数定义方法。
Y组合子
试图化简fac = w (fac' . w)
:
fac = w (fac' . w)
= (\fac' -> w (fac' . w)) fac' -- beta abstraction
= (\f -> w (f . w)) fac' -- name changing
我们发现了函数Y = \f -> w (f . w)
,使得fac = Y fac'
。这里的Y
函数称为Y组合子。
Y组合子的不动点性质
容易发现Y组合子的性质:forall f, Y f = f (Y f)
,以下是证明:
Y f
= w (f .w) -- apply Y
= (f . w) (f . w) -- apply the 1st w
= f (w (f . w)) -- apply the 1st .
= f (Y f) -- Y f = w (f .w)
我们把这个性质称为不动点性质。
通过不动点性质,Y组合子实现了对函数f
的无限递归调用:y f = f (f (f (f ...)))
。因此,可以将Y组合子和设置了停止条件的递归函数fac'
组合使用,实现递归函数的逻辑。
递归函数的非递归定义:一般方法+化简
我们可以把刚才的定义进一步化简:
-
递归函数
f = ...f...
-
提出非递归函数:
f' = \f -> ...f...
,满足f' f = f
-
借助Y组合子,定义递归函数:
f = Y f'
Y组合子的其他有关性质
简单地展开一下这个式子:Y = \f -> w (f . w)
Y = \f -> w (f . w)`
= \f -> (f . w) (f . w)
= \f -> (\x -> f (x x)) (\x -> f (x x))
我们把Y = \f -> (\x -> f (x x)) (\x -> f (x x))
这个式子称为Y组合子的curry定义。
参考
The implementation of functional programming languages