Lambda算子5a:Why oh why Y?

原创 2006年09月24日 14:13:00

  Lambda算子5a:Why oh why Y?

鄙视理论的老大们可以跳到下一篇文章,lambda算子5b。那里用JavaScript推导出Y。
 
原作在这里。我顺便参考了Richard Gabriel2001年写就的小文章The Why of Y。Gabriel也是个牛人。在Lisp的发展史里举足轻重。写了无数重量级的LISP系统不说,还领导了Common LISP下面的标准OO系统,CLOS,的标准制订和实现。他提出的Lisp性能基准测试方法至今有用。G老大除了技术牛以外,还有很强的政治手腕。当年曾在山头林立的LISP组织间斡旋,促成了Common LISP标准的问世。不愧是当过公司老总的人。G老大的文章也写得相当有煽动力。他的Worse Is Better已经是网上引用率最高的文章之一。有兴趣的还可以去读他的自传 里面有趣的故事不少,有些还非常激动人心。比如当年他被一个心胸狭隘的小人老师诬陷,没有进入MIT和哈佛,而后是怎么发奋的。现在G老大好像把兴趣转到写诗上面去了,从2000年开始就每日一诗。比赵丽华还勤奋。嗯,说远了。还是聊让人目眩的Y Combinator (组合子?)。
 
到上篇为止,我们已经一点一点把lambda算子建成一个有用的系统。我们搞定了数字布尔值,和选择操作 唯一剩下的东西就是重复操作,也就是循环了。
 
靠,这个问题就要稍稍棘手一点了。Lambda算子的确用递归来实现循环。不过lambda算子里的函数都没有名字,我们只好用一点小技巧来搞定佢。这个小技巧就是Y 组合子,也就是lambda的定点操作符。Y是Howard Curry在研究无类型的lambda算子时发现的。Y其实算最简单的组合子。复杂的还有着呢。可以想象我这种业余玩儿票的人学习编程语言理论时是多么痛苦。

先看一个非lambda算子的简单递归函数。嗯,猜对了,就是求阶乘,n!。没办法,标准例子嘛:
factorial(n) = 1 if n = 0
factorial(n) = n * factorial(n-1) if n > 0
 
如果我们现在就开始写出lambda算子版本的阶乘函数的话,我们还得需要一点工具。。。我们需要测试一个值是否等于零,需要一个函数求积,还需要一个递减函数。
 
我们可以用一个名叫IsZero的函数来测是否和零相等。IsZero带三个参数,一个数,和两个值。如果这个数是0, IsZero返回第一个值,不然就返回第二个值。
 
至于乘法嘛。没有递归前怎么能写出乘法嗫?所以我们暂时假设我们用乘法函数,Mult x y。

而最后是我们的递减函数。我们用Pred x 来计算x – 1。

嗯,现在我们可以来搞定阶乘函数了。递归部分暂时空白:
lambda n . IsZero n 1 (Mult n (something (Pred n)))

注意哈。我们不得不用something 暂时替代一下,因为我们没有任何函数名可用。不像上面的factorial(n) = n * factorial(n-1) if n > 0,要递归的时候调用同一个函数名,传入不同的参数就行了。所有现在问题就是:我们怎么才能让这个something 递归起来?

这个问题的答案就是所谓的组合子了。组合子是特殊的高阶函数(高阶函数的参数是函数,返回值也是函数)。定义这个函数时除了应用函数,不需要引用其它任何东西。Y组合子好像有种魔力,能让递归变得可能。它的定义如下:
 
let Y = lambda y . (lambda x . y (x x)) (lambda x . y (x x))

仔细观察一下这个定义,可以发现我们称这个函数为Y的原因在于它的形状。我们把上面的公式写成解析树的形式。把你的显示器倒过来,就可以看到一个接一个地Y了。^_^


Y组合子特别的地方在于把Y应用到Y身上后返回的是对这个应用自身的应用。也就是说,(Y Y) = Y (Y Y). 我们来推导一下 (Y Y)
  1. Y Y
  2. 展开第一个 Y:
    (lambda y . (lambda x . y (x x)) (lambda x . y (x x))) Y
  3. 来一把beta:
    (lambda x . Y (x x)) (lambda x. Y (x x))
  4. 第二个lambda式子里有x, 和第一个式子里的x冲突了,所以来个Alpha[x/z] :把第二个lmabda式子里的x换成z:
    (lambda x . Y (x x)) (lambda z. Y (z z))
  5. 再来Beta:
    Y ((lambda z. Y (z z)) (lambda z. Y (z z)))
  6. 把Y展开,然后对y和x做alpha变换:alpha[y/a][x/b]:
    (lambda a . (lambda b . a (b b)) (lambda b . a (b b))) ((lambda z. Y (z z)) (lambda z. Y (z z)))
  7. 再Beta,不要嫌长哈:
    (lambda b . ((lambda z. Y (z z)) (lambda z. Y (z z))) (b b)) (lambda b . ((lambda z. Y (z z)) (lambda z. Y (z z))) (b b))
现在仔细观察这个表达式。正是Y (Y Y)!要记得(Y Y) 恰好等于上面第3条的结果, (lambda x . Y (x x)) (lambda x . Y (x x))再结合Y的定义,自然得到 Y Y = Y (Y Y) 这也是Y的魔力:通过把自身应用到自身,它再造了自己:(Y Y) = Y (Y Y) = Y (Y (Y Y)),  子子孙孙,无穷无尽。Y最重要的特性便是Y F = F (Y F)。Y一附身,就有定点了。神奇得紧啊。
 
那我们怎么运用这个疯狂的东东嗫?

嗯,我们做一点点尝试先。先用一个名字(就像写普通的递归函数一样)试试:
let fact = lambda n . IsZero n 1 (Mult n (fact (Pred n)))

现在的问题是,”fact”并不是一个在fact内定义好的标识符。我们怎么才能让lambda式子内的”fact”指向”fact”的函数定义?思考ing….嗯,我们可以再做一个lmabda函数,以便我们把”fact”函数当成参数传进去,然后如果我们可以想法写出一个能把自己当成参数传给自己的”fact”, 就大功告成了。我们把这种自己玩儿自己的函数叫做metafact:
let metafact = lambda fact . (lambda n . IsZero n 1 (Mult n (fact (Pred n))))

注意哈,(metafact fact) n = fact n。也就是说,fact是metafact这个函数的一个定点。定点的定义其实很简单:F(X) = X的话,X就是函数F的一个定点。当年高一时好像就学了这个概念。想不到的是这个看似简单的概念竟然在许多计算机科学理论和应用里扮演重要角色。比如程序分析,比如格点理论,比如模型检验,比如µ-算子,比如拓扑。
 
现在我们再把metafact应用到它自身,就得到了阶乘函数:
fact n = (metafact metafact) n.

这里揭示了一个非常重要的技巧:我们实现函数自我引用(self-reference)的基本方法就是把一个函数应用到他自身。而通过自我引用完成递归就是Y起作用的地方。它让我们构建一个怪异的结构。在这种结构里,上面式子里的函数可以被复制到我们需要递归的地方。如下式子可以实现我们的构想: metafact (Y metafact) 。把它展开:

(lambda fact . (lambda n . IsZero n 1 (Mult n (fact (Pred n))))) (Y (lambda fact . (lambda n . IsZero n 1 (Mult n (fact (Pred n))))))

(Y metafact) 就是lambda函数metafact参数fact的值。当我们对该函数应用β转换时,如果n为0, 函数返回1。如果n不为0,我们则调用Mult n (fact (Pred n))。对Fact应用beta转换, 我们得到Y metafact (就是我们传进去的参数哈)。结合Y的疯狂魔术,我们得到 metafact (Y metafact) (Pred n)。
 
哈哈!递归。变态之极的递归。

原文作者在大学里学到Y组合子时—应该是1989年左右—直到现在作者还觉得Y神秘。虽然作者现在理解Y了,但还是很难想象怎么有人能想出Y这个东西!
 
如果你对Y有兴趣,我们强烈推荐The Little Schemer这本书。该书非常精彩,写得象一本儿童读物。书里要么每一页正面是一个问题,背面就是答案,要么一页分成两栏,一栏问题一栏答案。书的风格轻松幽默,不仅教你Scheme编程,更教人怎么思考。
 
其实Y组合子有好几个版本。计算lambda表达式的方式有好几种。给出一个如下表达式:
(lambda x y . x * y) 3 ((lambda z. z * z) 4)

我们可以采用不同的顺序来计算:先对(lambda x y . x * y )beta变换,得到
3 * ((lambda z . z * z) 4)

或者,我们可以对((lambda z . z * z) 4)beta变换先:
(lambda x y . x * y) 3 (4 * 4)

这个例子里,两种不同的方法得出同样的结果。但对有些表达式却不然。第一种顺序叫做lazy evaluation:只有在需要一个函数时我们才计算它。第二种顺序叫做eager evaluation:我们总是计算出一个参数的值再把该参数传给一个函数。编程语言里面,Lisp, Scheme, ML这三种基于lambda算子的语言采用eager evaluationHaskellMrianda这两种基于lambda算子的语言采用lazy evaluation。这篇帖子里的Y组合子是对lazy evaluationY。如果我们采用eager evaluation,前述的Y组合子就失效了――它会不停拷贝Y,形成死循环。

图灵机停机问题的不可判定性

Turing Machine Halting Problem停机问题:指判断任意一个程序是否能在有限的时间之内结束运行的问题。图灵机停机问题是不可判定的,意思即是不存在一个图灵机能够判定任意图灵机对于...
  • Zyj061
  • Zyj061
  • 2017-04-09 23:04:57
  • 1777

图灵停机问题(halting problem)

问题描述 是否存在一个过程能做这件事:该过程以一个计算机程序以及该程序的一个输入作为输入,并判断该过程在给定输入运行时是否最终能停止。 问题解答 1936年图灵证明这样的过程是不存在的。 证明 ...
  • MyLinChi
  • MyLinChi
  • 2018-01-12 15:00:57
  • 138

Lambda算子5b:How of Y

Lambda算子5b:How of Y  其实是这篇文章的意译。有些东西省了。添了点私货。就有了下面的帖子。虽然Y相当神奇。对它的推导也不完全是天外飞仙般无迹可寻。基本上我们为了解决让没有名字的函数能...
  • g9yuayon
  • g9yuayon
  • 2006-09-24 14:28:00
  • 13984

C++实现的lambda Y算子

#include #include class FUNC{public : virtual int operator()(int a, int b) {  return a + b; }};class...
  • yujunlong2000
  • yujunlong2000
  • 2008-10-09 15:15:00
  • 555

can<em>y算子</em>图像处理

can<em>y算子</em>进行边缘检测的matlab程序,对初学者了解can<em>y算子</em>很有帮助哦 综合评分:4 收藏评论(2)举报 所需: 3积分/C币 下载个数: 11 开通VIP 立即下载 ...
  • 2018年04月15日 00:00

Codeforces 4C Registration system (map)

 A new e-mail service "Berlandesk" is going to be opened in Berland in the near future. The sit...
  • nare123
  • nare123
  • 2016-03-17 18:43:55
  • 185

Why C++?王者归来

因为又有人邀请我去Quora的C2C网站去回答问题去了,这回是 关于 @laiyonghao 的这篇有点争议的博文《2012 不宜进入的三个技术点》ActionScript,Thread 和 C+...
  • songjinshi
  • songjinshi
  • 2012-09-08 13:51:10
  • 2530

Android Jni OpenCv 利用Cany算子做边缘检测

一,利用OpenCV提供给Java封装好的调用摄像头接口,获取MAt @Override public void onCameraViewStarted(int width, int height...
  • Jason101123
  • Jason101123
  • 2017-11-17 15:16:49
  • 68

Sobel算子

幻灯片1 Sobel算子  幻灯片2 一、Sobel边缘检测算子 l 在讨论边缘算子之前,首先给出一些术语的定义: l (1)边缘:灰度或结构等信息的突变处,边缘是一个区域的结束,也是另一个区域的开始...
  • GoodShot
  • GoodShot
  • 2013-08-22 09:22:22
  • 58599

why-not 和 why 问题简介

数据库中的why-not问题与why问题,用来描述数据库的查询结果与期望不同的情况。问题描述why-not问题,表示数据库的查询结果中缺失了部分期望得到的结果,按字面意思:为什么我希望的结果没有出现?...
  • u012436758
  • u012436758
  • 2017-01-11 20:04:50
  • 308
收藏助手
不良信息举报
您举报文章:Lambda算子5a:Why oh why Y?
举报原因:
原因补充:

(最多只允许输入30个字)