推导Y组合子

推导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 fh 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。在这种情况,称facfac'不动点,就像数学里对不动点的定义一样。

对比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

重新发明 Y 组合子 JavaScript(ES6) 版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值