停机问题与Y Combinator(二)

Y Combinator

停机问题在上一篇文章已经了解过了,这篇文章让我们来了解一下Y组合子。其实说了解也并不恰当,只能说是我们用Scheme语言一步一步的推导出了Y组合子,至于了解,留给下一篇文章。上一篇文章中提过我是看《The Little Schemer》才写的这系列文章(实际上是因为组里人让技术分享,反正都要准备,不如记下来),但是实际上这篇文章中的推导思路和Dan的《The Little Schemer》中的思路并不一样。我个人认为我的思路更好理解一些,但是也是在Dan书的启发下才想出来的。在下一篇文章中还会有一种全新的思路的去推导Y组合子,会让人感觉豁然开朗。

来思考这样一个问题:如果没有define,也就是说我们没法给函数起字,我们怎么写一个递归程序?(你怎么这么多问题?你就是想装B)回顾之前的length函数:

(define length
  (lambda (l)
    (cond ((null? l) 0)
          (else (add1 (length (cdr l)))))))

既然没有名字了,那怎么着。那把用到length的地方都换成lambda表达式吧,这是个很直接的想法!但是这么写有问题啊,用lambda表达式替换length,新的lambda表达式里面还有length啊,这么写根本写不完啊!怎么办?那这样吧,新的lambda表达式里面把length替换成之前写的eternity(停机问题与Y Combinator(一))吧,这样我们的程序对于空的list或者只含有一个元素的list是可以返回正确的结果的。代码就是这个样子:

;;; length <= 1
(lambda (l)
  (cond
   ((null? l) 0)
   (else
    (add1
     ((lambda (l)
        (cond
         ((null? l) 0)
         (else
          (add1
           (eternity (cdr l))))))
      (cdr l))))))

所以长度小于等于2的?

;;; length <= 2
(lambda (l)
  (cond
   ((null? l) 0)
   (else
    (add1
     ((lambda (l)
        (cond
         ((null? l) 0)
         (else
          (add1
           ((lambda (l)
              (cond
               ((null? l) 0)
               (else
                (add1
                 (eternity (cdr l))))))
            (cdr l))))))
      (cdr l))))))

我们写了这么长的代码只支持两个元素,这咋办?要不写个支持一千个元素的,然后多于一千个就提示个错误?强行纯手工递归,66666!要是这么写这个函数是写不完的,不过我们发现这个东西有很多地方都是重复的,可不可以抽象一下?我们仔细看一下之前的两个函数,有一块代码好像出现了两次,根据DRY原则,我们应该抽出来啊。要不违反原则,那怎么行啊:)。开个玩笑,那些原则看看笑笑就过了,那些写X原则X模式的书怎么看都像传销手册。好,不扯别的,我们来看是哪块代码重复了。

(lambda (l)
  (cond
   ((null? l) 0)
   (else
    (add1
     (eternity (cdr l))))))

看看这块代码能干什么?好像是对于空的list,它能够正确计算空的list的长度。再看能过计算长度为1的代码,好像是把原来length的地方替换成长度为0的就可以了。长度为2的那个函数,就是把length替换成长度为1那个的函数,是这样吧,那我们加个参数?

所以能够正确计算长度为0的list的函数?

((lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (length (cdr l)))))))
 eternity)

好,写一下最大长度为1和2的函数吧:

;;; 长度 <= 1
((lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (length (cdr l)))))))
 ((lambda (length)
    (lambda (l)
      (cond
       ((null? l) 0)
       (else (add1 (length (cdr l)))))))
  eternity))
  
;;; 长度 <= 2
((lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (length (cdr l)))))))
 ((lambda (length)
    (lambda (l)
      (cond
       ((null? l) 0)
       (else (add1 (length (cdr l)))))))
  ((lambda (length)
     (lambda (l)
       (cond
        ((null? l) 0)
        (else (add1 (length (cdr l)))))))
   eternity)))

这个就是按照上面的思路的写出的函数,仔细看看是那么回事吧。我们发现上面好像还是有那么一坨,那可是重复了一遍又一遍啊!所以加个参数,然后把那一坨传进去吧。这次一次性的都写出来,不bb直接上代码:

;;; 计算长度为0的
((lambda (mk-length)
   (mk-length eternity))
 (lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (length (cdr l))))))))
      
;;; 所以计算为1的?
((lambda (mk-length)
   (mk-length
    (mk-length eternity)))
 (lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (length (cdr l))))))))

;;; 计算为2的,哎!已经简单到我忍不住想写长度为3的了。
((lambda (mk-length)
   (mk-length
    (mk-length
     (mk-length eternity))))
 (lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (length (cdr l))))))))

看起来已经比最开始的版本简洁多了,写个千八百遍的好像不成问题。但是作为超哥的同事,都是追求完美的人,这不能忍啊!思考个问题?我们为什么要把eternity传进去啊,那个好像是我们随便决定的,我们发现每调用一次mk-length好像就是一个可以计算多一个元素的函数。那既然这样,我们干脆做一个猜想:把mk-length作为初始的参数传进去,然后在length处,自己调用一下自己,好像就可以递归了。既然有了猜想,那就试试吧!

;;; 这是函数
((lambda (mk-length)
   (mk-length mk-length))
 (lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 ((length length)
                   (cdr l))))))))

;;; and我们做个测试
(((lambda (mk-length)
   (mk-length mk-length))
 (lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 ((length length)
                   (cdr l))))))))
 (list 1 2 3 4 5 6))

;;; and纯lambda版
((lambda (mk-length)
   (mk-length mk-length))
 (lambda (length)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else
       ((lambda (x)
          (+ x 1))
        ((length length)
         (cdr l))))))))

测试的代码返回了6!(我真的没作弊,之前咱们最多就写到2)卧槽成功了,真的成功了!!!难道永动机是存在的?能量守恒定律是错的?YY的有点多。这个猜想多么重要,所以说学习在于培养直觉,三更灯火五更鸡的,除了感动了自己并没有什么卵用。

所以Y组合子是啥?别着急,还没粗线呢。我们把(length length)的调用作为一个参数传进来,然后Currying一下,就是下面这样:

;;; 把(length length)作为一个参数

((lambda (mk-length)
   (mk-length mk-length))
 (lambda (length)
   (lambda (l)
     ((lambda (function l1)
        (cond
         ((null? l1) 0)
         (else (add1 (function (cdr l1))))))
      (length length) l))))

;;; Currying 不懂Currying没关系,只要知道这两个是等价就可以
((lambda (mk-length)
   (mk-length mk-length))
 (lambda (length)
   (lambda (l)
     (((lambda (function)
         (lambda (l)
           (cond
            ((null? l) 0)
            (else (add1 (function (cdr l)))))))
       (length length)) l))))

在上面的代码中,你把形参function换做length,是不是有一段代码很是眼熟?是吧?提出来,作为一个参数传进去。

((lambda (le)
   ((lambda (mk-length)
      (mk-length mk-length))
    (lambda (length)
      (lambda (x)
        ((le
          (length length)) x)))))
 (lambda (function)
   (lambda (l)
     (cond
      ((null? l) 0)
      (else (add1 (function (cdr l))))))))

看看上面的调用,参数也就是下半部分,好像就是我们写的length。而操作符即上半部分,好像就是一个类似mk-length的东西,仔细看看是不是?

然而这个操作符有另一个名字——Y Combinator !

(define Y
  (lambda (f)
    ((lambda (g)
       (g g))
     (lambda (h)
       (lambda (x)
         ((f (h h)) x))))))

下集预告����

前面说了这篇主要讲用Scheme语言一步一步推导出Y组合子,既然已经推导出了Y组合子,本着单一职责原则,这篇文章到此就结束了。有这么几个问题需要思考一下:

  • 我们虽然推导出了Y组合子,但是它究竟干了点啥?为什么就能递归了?
  • 如果看过λ演算版的Y组合子,你会发现和我们推导出的并不一样,这是为什么?
  • 写Y组合子就写Y组合子吧,为什么要提停机问题?

上面的问题在下一篇文章中都会得到答案,还会给大家讲讲写递归程序的终极要义,敬请期待:)。(简书有语法高亮了)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容