王垠的「40 行代碼」

"我有什麼資格說話呢?如果你要了解我的本事,真的很簡單:我最精要的代碼都放在 GitHub 上了。但是除非接受過專門的訓練,你絕對不會理解它們的價值。你會很難想象,這樣一片普通人看起來像是玩具的 40 行 cps.ss 代碼, 融入了我一個星期的日日夜夜的心血,數以幾十計的推翻重寫。這段代碼,曾經耗費了一些頂尖專家十多年的研究。一個教授告訴我,光是想看懂他們的論文就需要 不止一個月。而它卻被我在一個星期之內悶頭寫出來了。我是在說大話嗎?代碼就擺在那裏,自己去看看不就知道了。當我死後,如果有人想要知道什麼是我上半生 最重要的“傑作”,也就是這 40 行代碼了。它蘊含的美,超越我給任何公司寫的成千上萬行的代碼。"


有沒有人來說說這個東西,我想知道他有沒有說大話。

附代碼:

;; A simple CPS transformer which does proper tail-call and does not;; duplicate contexts for if-expressions.;; author: Yin Wang ([email protected])(load "pmatch.scm")(define cps
  (lambda (exp)
    (letrec
        ([trivial? (lambda (x) (memq x '(zero? add1 sub1)))]
         [id (lambda (v) v)]
         [ctx0 (lambda (v) `(k ,v))]      ; tail context
         [fv (let ([n -1])
               (lambda ()
                 (set! n (+ 1 n))
                 (string->symbol (string-append "v" (number->string n)))))]
         [cps1
          (lambda (exp ctx)
            (pmatch exp
              [,x (guard (not (pair? x))) (ctx x)]
              [(if ,test ,conseq ,alt)
               (cps1 test
                     (lambda (t)
                       (cond
                        [(memq ctx (list ctx0 id))
                         `(if ,t ,(cps1 conseq ctx) ,(cps1 alt ctx))]
                        [else
                         (let ([u (fv)])
                           `(let ([k (lambda (,u) ,(ctx u))])
                              (if ,t ,(cps1 conseq ctx0) ,(cps1 alt ctx0))))])))]
              [(lambda (,x) ,body)
               (ctx `(lambda (,x k) ,(cps1 body ctx0)))]
              [(,op ,a ,b)
               (cps1 a (lambda (v1)
                         (cps1 b (lambda (v2)
                                   (ctx `(,op ,v1 ,v2))))))]
              [(,rator ,rand)
               (cps1 rator
                     (lambda (r)
                       (cps1 rand
                             (lambda (d)
                               (cond
                                [(trivial? r) (ctx `(,r ,d))]
                                [(eq? ctx ctx0) `(,r ,d k)]  ; tail call
                                [else
                                 (let ([u (fv)])
                                   `(,r ,d (lambda (,u) ,(ctx u))))])))))]))])
      (cps1 exp id))));;; tests;; var(cps 'x)(cps '(lambda (x) x))(cps '(lambda (x) (x 1)));; no lambda (will generate identity functions to return to the toplevel)(cps '(if (f x) a b))(cps '(if x (f a) b));; if stand-alone (tail)(cps '(lambda (x) (if (f x) a b)));; if inside if-test (non-tail)(cps '(lambda (x) (if (if x (f a) b) c d)));; both branches are trivial, should do some more optimizations(cps '(lambda (x) (if (if x (zero? a) b) c d)));; if inside if-branch (tail)(cps '(lambda (x) (if t (if x (f a) b) c)));; if inside if-branch, but again inside another if-test (non-tail)(cps '(lambda (x) (if (if t (if x (f a) b) c) e w)));; if as operand (non-tail)(cps '(lambda (x) (h (if x (f a) b))));; if as operator (non-tail)(cps '(lambda (x) ((if x (f g) h) c)));; why we need more than two names(cps '(((f a) (g b)) ((f c) (g d))));; factorial(define fact-cps
  (cps
   '(lambda (n)
      ((lambda (fact)
         ((fact fact) n))
       (lambda (fact)
         (lambda (n)
           (if (zero? n)
               1
               (* n ((fact fact) (sub1 n))))))))));; print out CPSed function(pretty-print fact-cps);; =>;; '(lambda (n k);;    ((lambda (fact k) (fact fact (lambda (v0) (v0 n k))));;     (lambda (fact k);;       (k;;        (lambda (n k);;          (if (zero? n);;            (k 1);;            (fact;;             fact;;             (lambda (v1) (v1 (sub1 n) (lambda (v2) (k (* n v2))))))))));;     k))((eval fact-cps) 5 (lambda (v) v));; => 120

謝謝邀請。我不算很熟悉Scheme,只能勉力爲之。我知道我的解讀也許有錯,我也邀請了我熟悉的朋友來回答。他比我懂得更全,應該有幫助。

=== 07/29/2013 更新 ===
當事人到場了。我畢竟是個業餘搞函數式編程的。大家還是不要看我這裏,看的原版解釋吧。
===================

我大概讀過這段代碼:github.com/yinwang0/gem。 簡單地說,這段代碼做了兩件事,一件事是CPS,也就是自動尾遞歸,第二件事則是用Scheme語言寫了一個Scheme的解釋器。通過他給出的cps函 數,我可以用Scheme這個語言的符號系統重新定義所有Scheme的關鍵字,並執行正確的程序語義。換言之,它可以讓這個語言自己解釋自己。本質上, 他的代碼是在模仿當初 John McCarthy 發明 Lisp 語言時給出的代碼,但用了Scheme風格重寫了一遍。

這段代碼裏 有一些相當有技巧性的部分。主要是那個cps1函數。我承認我也沒有完全看懂,但大概能理解它在保持語義的同時基本做到了語言元素的最小化。他的代嗎的 31行和37行就是最關鍵的部分,實現了條件分支和遞歸調用。基本的原理並不複雜,主要是利用了Scheme的列表解構拆解元素,最終落實到條件分支和函 數調用。如果說得更Scheme風格一點,這個cps函數就是一個自己實現的eval函數。當然是簡化了一些,沒有實現一些更誇張的功能,比如call- with-current-continuation。

注:這個cps的實現中只包含了很少的幾個語言特性:定義常量,定義函數,分支(if)和遞歸。這是滿足一個有意義的最小化描述必需的。如果任意引入語言元素,比如while,循環,則可能就會出現語言元素爆炸的情況,陷入無限自證的邏輯怪圈裏去。

對這段代碼,我自己的建議是,大家可以不必太在乎王垠的宣言。能寫出這段代碼的人,無疑非常熟悉符號推理的一般規則,也具備相當深厚的數學功底,一般人確實是寫不出來。這也符合我對王垠學識的印象。但我也得說,這段代碼對多數工程師而言並沒有實際價值。不懂也無妨。

======
對不熟悉編譯原理和符號推理的朋友們來說,這裏可能需要一些額外的說明。請參見下方。

在 編譯原理的世界裏,自舉是一個很重要的話題。一個很經典的例子:GCC語言的編譯器是C語言寫的,但第一個GCC編譯器是用另一個編譯器編譯的;那麼順着 這個根源向下跟蹤,我們遲早必須回答這個問題,即世界上第一個編譯器是什麼語言寫的——答案是彙編。那麼這樣下去,我們最終發現,任何程序設計語言都不能 完全用自己描述自己。

從工程角度上說,這個問題倒不影響什麼。但是從數學角度上看,這個缺陷則讓很多人頭疼不已,因爲它破壞了所謂數學的「美」的原則。這裏的「美」,實際的含義是自解釋。很多符號邏輯研究者都熱衷於找到一種符號體系,能夠使用有限的符號系統描述自身。只要找到了這一點,整個解釋器的設計可以成爲一個自己證明自己的,封閉的體系

喜歡浪漫的文科朋友們可能會記得希臘神話中的烏洛波洛斯,一條首尾相連象徵無窮無盡的蛇。是的,所謂自舉就是符號推演世界的烏洛波洛斯,一種純粹的數學上的和諧和優雅。

可惜對我這個哥德爾定理的信徒而言,這種數學上的美是毫無價值的東西。因爲在我的邏輯體系裏,這個世界裏沒有可以自證自身的公理體系。

大概就是這樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章