匿名函數在不少語言中的表現形式大概以下:編程
(lambda (n) (* (+ n 1) (- n 1)))
只有參數列表和函數體,而沒有名字。在大部分狀況下沒問題,可是一旦須要用到遞歸的話,就有點麻煩了,由於不知道如何去遞歸的調用一個匿名函數。app
在學術界中有一些解決這個問題的辦法,其中一個就是Y組合子,可是那個太繁瑣,並且難以經過宏自動將一個lambda變成可遞歸形式,沒什麼好處。編程語言
根據歷史經驗,目前比較好的辦法,就是實現一個操做符,匿名函數經過這個操做符來調用自身:函數
(lambda (n) ... (this (- n 1))) 或者是 (lambda (n) ... (lambda (- n 1)))測試
第一種是用this或其餘東西來表示當前匿名函數自己,直接調用就能夠遞歸。第二種是和有名函數同樣,用和定義匿名函數同樣的操做符來調用自身。this
然而第二種不實際,由於這樣會形成混亂,好比須要嵌套lambda時,並且其語義也不對。spa
因此此文主要圍繞第一種方式:實現讓this指向當前匿名函數,從而能夠遞歸調用自身。設計
NewLisp是一個Lisp語言的實現,也能夠說是一個方言,其與Common Lisp相比,少了不少東西,但遠比Common Lisp容易使用。Lisp系列的語言有一個特色:沒有語法。或者說極小語法,用Lisp編寫程序,直接沒有了語法階段,從語義開始起步,因此很是接近編譯器。Lisp是一個多範式編程語言,支持命令式、函數式、面向對象等等,固然Lisp其實應該被稱爲:列表處理語言。或者說是:抽象語法樹處理語言。源程序就是AST,經過設計AST來構建軟件。rest
首先咱們找個最簡單的辦法,而後逐步改善。code
在匿名函數內部定義一個局部函數,經過調用這個函數,就能夠實現遞歸自身了。好比在要定義的遞歸匿名函數中再定義一個函數做爲主體函數,經過調用這個函數來實現遞歸的效果。
大概的形式以下:
(lambda (n) (define this (n) (if (< n 2) 1 (* n (this (- n 1))))) (this n))
如今能夠設計一個定義可遞歸匿名函數的宏了:
(define-macro (lambda* _args) (letex ((fargs _args) (fbody (cons 'begin $args)) (fcall (cons 'this (flat _args)))) (lambda fargs (define (this fargs) fbody) fcall)))
這樣,只須要用lambda*,就能夠定義一個可遞歸的匿名函數:
; 經過this來調用自身 (lambda* (n) (if (< n 2) 1 (* n (this (- n 1)))))
可是這樣有一個很大的問題,這個定義的函數會污染名稱空間,並且不一樣的lambda*會覆蓋掉this,由於define是在全局中定義的。在Common Lisp中,能夠經過定義僅在函數內部可見的嵌套函數來解決:
(defmacro re-lambda (&rest body) `(lambda (&rest args) (labels (,(cons 'this body)) (apply #'this args))))
可是在NewLisp中,我沒有找到如何定義僅在函數內部可見的嵌套函數,因此還須要經過其餘的辦法才能解決。
因而我想到了自動生成函數名的方式,NewLisp有一個函數(time-of-day),返回一個以較爲精確的時間戳:
> (time-of-day) 66359119.140 > (time-of-day) 66359415.039
這樣咱們能夠在擴展宏時自動生成一個函數名,以免衝突:
(define-macro (lambda* _args) (let ((f (string "$."(time-of-day)))) ;生成一個隨時間變化的函數名 (eval (list 'define (cons (sym f) (flat _args)) (list 'let (list (list 'this (sym f))) ; this指向這個函數 (cons 'begin $args))))))
因而當用lambda*定義時,會自動生成一個每一個時刻都不一樣的名稱,好比$.66789291.99二、$.66922513.671,幾乎不會有重複。
可是這樣依然不行,由於只是避免的名稱衝突,但仍是會污染名稱空間,並且在某些狀況下依然會形成難以預料的問題。因此在最後,設計了一個終極版本的lambda*,沒有任何負面做用。
終極版本
(define-macro (lambda* _args) (letex ((fargs _args) (fbody (cons 'begin $args)) (fcall (cons 'this (flat _args)))) (lambda fargs (let ((this (lambda fargs fbody))) fcall))))
經過在擴展宏時,在lambda內部在定義一個lambda做爲主體函數,使用let將局部變量this指向這個主體函數,因而就能夠經過this來模擬調用自身了,函數不須要有名字,而只有一個指向函數的局部變量this,效果很是好:
; 階乘函數 (lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) ; 宏展開後以下=> (lambda (n) (let ((this (lambda (n) (begin (if (< n 2) 1 (* n (this (- n 1)))))))) (this n)))
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 1) 1 > ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 2) 2 > ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 3) 6 > ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 4) 24 > ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 5) 120 > ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 20) 2432902008176640000 > this nil > (setf this 100) 100 > ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 10) 3628800 > this 100
* 勿輕易嘗試Lisp的宏,更不要輕易嘗試NewLisp的宏,前者你會受傷,後者你能體會到和查C++模板錯誤同樣的過程,甚至更加爆炸。
用農業界的一個術語來講:就像一顆原子彈。