Lambda算子5b:How of Y

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

Lambda算子5b:How of Y

 其实是这篇文章的意译。有些东西省了。添了点私货。就有了下面的帖子。
虽然Y相当神奇。对它的推导也不完全是天外飞仙般无迹可寻。基本上我们为了解决让没有名字的函数能自我引用,一步一步抽象出了Y。所以知道Y的推导过程对我们程序员还是很有意义的:毕竟编程的过程也是抽象的过程。看看当年的老大们怎么从纷繁的表象里抽象出一般规律,对我们日后的思考应该大有好处。为了老大们能够试验,我就用JavaScript了。整个推导的过程非常像编程时的重构。我们提出一个粗略的解决方案,然后仔细观察,找出可以抽象的地方,进行抽象。得到一个更普适的结果后,继续重复重构的步骤,直到得到最优解。估计看这篇帖子的人都知道怎么玩儿小小JavaScript吧?再说有个浏览器就有了测试环境。废话少说,看代码。我们还是以阶乘函数为例。先看通常的写法:
 
1: function fact(n){
2:         if(n == 0){
3:                 return 1;
4:         }
5:
6:         if(n > 0){
7:                 return n * fact(n - 1);
8:         }
9: }
上面的JavaScript函数定义内部调用自己。这种调用可行的前提是我们用函数名指代函数定义。也就是说,fact这个名字绑定的函数定义就是上面的函数体。如果我们不能通过名字来调用函数怎么办呢(就跟lambda算子一样)?也许有老大会问:为什么增加这个限制呢?不是自虐么?理由很简单:理论需要探求事物本质。记得奥卡姆剃刀吧?如无必要,毋增实体。函数名到底是必需元素,还是句法糖?这种研究方法也有实际的意义:再复杂的系统也是在简单但完备的基础上搭建起来的。强大的编程工具,总是基于于层层叠加的抽象,而最低级的抽象层总是非常简单。简单意味着透彻,简单意味着健壮。简单意味着灵活。简单意味着经济。问题是,到底简单到什么地步?怎么保证系统不至于简单到一无所用的地步?这和逻辑学家建立系统时总是要证明系统的正确性和完备性一个道理。而找到了Y,我们也就明白了,原来函数名绑定并非本质。
 
嗯,继续。函数fact是递归的基本形式。既然我们不能直接在函数体内通过函数名调用另一个函数,我们至少可以把想调用的函数通过参数传进去。于是我们得到fact2:
09: fact2 = function(himself, n){
10:         return function(n){
11:                 if(n < 2){
12:                         return 1;
13:                 }
14:
15:                 return n * (something_expression_with_himself);
16:         }
17: }
 
我用JavaScript里的匿名函数来强调lambda函数不具名的特征。这里用fact2纯粹为了方便。变量fact2本身并没有参与运算。如果我们这样调用:fact2(fact2, 3),那fact2这个函数不就可以调用自身了么?这也就是前面提到的自我引用的技巧:加一层抽象,从而把自己通过参数传给自己。现在我们要解决的就是,到底something_expression_with_himeself是什么东西。因为fact2和himself都必须递归调用自己,something_expression_with_himself从直觉上应该是himself(himself, n-1)开始。看到没有?函数体和调用方式不变,但n变成n-1了。和递归一致了哈:
19: fact3 = function(himself, n){
20:         if( n < 2 ){
21:                 return 1;
22:         }
23:
24:         return n * himself(himself, n-1);
25: }
 
 
在你的JavaScript console里试验一下:fact3(fact3, 3) 的确返回6。什么,没有JavaScript console? 老大啊,上网的银能不用FireFox么?用了FireFox的程序员能不用FireBug么?快去下载吧。
 
下载完了?那我们继续。记得lambda算子里的函数只接受一个参数吧?可是fact3接受两个函数。所以我们要撒一点“咖喱”:
17: fact4 = function(himself){
18:         return function(n){
19:                 if( n < 2 ){
20:                         return 1;
21:                 }
22:
23:                 return n * himself(himself)(n-1);
24:         }
25: }
 
运行一下三。看见才能相信哈。fact4(fact4)(3) = 6, fact4(fact4)(4) = 24, fact4(fact4)(5)=120。。。。
 
现在的问题是,我们要把这个具体例子的递归方法抽象出来。现在我们需要把和自我引用无关的细节同自我引用本身分开。因为我们关心的是怎么运用自我引用来解决递归问题。我们可以先解决分离himself和n有关的代码。这样做是因为管理n的代码只与求阶乘有关,不是我们关心的重点。好比分离框架逻辑和业务逻辑。于是我们得到函数fact5。
37: fact5 = function(h){
38:         return function(n){
39:                 var f = function(q){
40:                         return function(n){
41:                                 if(n < 2){
42:                                         return 1;
43:                                 }
44:
45:                                 return n * q(n-1);
46:                         }
47:                 }
48:
49:                 return f(h(h))(n);
50:         }
51: }
 
运行几个例子增强点信心哈:fact5(fact5)(3) = 6, fact5(fact5)(4)=24。。。
 
注意函数f其实不用嵌在fact5里面。所以我们可以写成:
37: fucntion f(q){
38:         return function(n){
39:                 if(n < 2){
40:                         return 1;
41:                 }
42:
43:                 return n * q(n-1);
44:         }
45: }
46:
47: function fact5(h){
48:         return function(n){
49:                 return f(h(h))(n);
50:         }
51: }
现在就可以看出两件事了:一是新的函数f不过是参数化的阶乘函数 ―― 递归部分变成参数(也是一个函数)q了。第二,我们可以把f进一步抽象出来,剥离具体的阶乘部分,于是得到了Y:
69: function Y(f){
70:          g = function(h){
71:                 return function(x){
72:                         return f(h(h))(x);
73:                 }
74:          }
75:
76:          return g(g);
77: }
现在我们可以用Y来实现阶乘了:
79: fact6 = Y(
80:         function(h){
81:                 return function(n){
82:                         if(n < 2){
83:                                 return 1;
84:                         }
85:
86:                         return n * h(n-1);
87:                 }
88:         }
89: ); 
试一下,比如fact6(3) = 6, fact6(4) = 24。。。。
 
长出一口气。连写了4个小时,终于把关于Y的东西写完了。下面可以谈更宽泛的组合算子了。主要是S,K,和I三个算子。有个变态编程语言unlambda就是靠解析SKI为生的。
 

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

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

图灵停机问题(halting problem)

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

Lambda算子5a:Why oh why Y?

  Lambda算子5a:Why oh why Y? 鄙视理论的老大们可以跳到下一篇文章,lambda算子5b。那里用JavaScript推导出Y。 原作在这里。我顺便参考了Richard Gabri...
  • g9yuayon
  • g9yuayon
  • 2006-09-24 14:13:00
  • 10390

Lambda算子5b:How of Y

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

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

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

lambda算子简介1.a

接着前两天的转载继续写。上次说到lambda算子的函数只接受一个参数。那怎么处理多个参数呢?如果只有一个参数,那岂不是连加法都不能实现?这当然难不倒像丘齐这样的天才。于是, lo and behold...
  • g9yuayon
  • g9yuayon
  • 2006-05-29 08:30:00
  • 15044

lambda算子 1.b

上上周就快写完这篇时,IE突然当掉,写的东西烟消云散。俺也元气大伤。这次吸取教训,不用CSDN的在线工具写了。这样的坏处是把文章拷贝到CSDN时,格式难免出错,还得手工调整一下。CSDN什么时候可以实...
  • g9yuayon
  • g9yuayon
  • 2006-06-12 11:14:00
  • 10913

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

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

Sobel算子

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

lambda算子3:阿隆佐.丘齐(Alonzo Church)的天才

lambda算子3阿隆佐.丘齐(Alonzo Church)的天才:Lambda算子里的数前面建立了lambda运算的基本规则,就可以用lambda算子做点有意思的东西了。开始前为方便计,我们先来点语...
  • g9yuayon
  • g9yuayon
  • 2006-08-14 14:45:00
  • 12360
收藏助手
不良信息举报
您举报文章:Lambda算子5b:How of Y
举报原因:
原因补充:

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