Unicorn -- 我的第一個玩具Lisp解釋器

一開始的時候,我只是希望她能遞歸的執行計算,比如n的階乘:

(define fact (lambda (n)
    (if (< n 1) 1 (* n (fact (- n 1))))))

看到這麼多括號,我猜你已經被自動勸退了。但是相信我,這只是爲了省掉寫parser的時間。所以,只需要加一點點語法糖,她就可以變成下面的樣子:

(define-function fact(n)
    (if (< n 1)
        1
    (* n (fact (- n 1)))
))

跟Java很像不是麼~

從一個計算器開始

所以,她和大學時代你用C寫的那個計算器有什麼區別?

從本質上來說其實並沒有。

回憶一下,你從stdin讀進來3 + 4,然後解析出+34這幾個元素,接着 把加法作用於34的值

爲了方便,我們可以讓輸入的格式是形如:(* (+ 1 2) (+ 3 4))的樣子,所以你依然把輸入解析爲*(+ 1 2)(+ 3 4),然後繼續用剛纔的方式 把乘法作用於(+ 1 2)(+ 3 4)的值

讓我們簡單抽象一下,輸入的表達式(Expression)可以分爲幾個部分:

(operator expression1 expression2)

(這裏的第一個參數operator,實際上也可以是expression)

因此,她的內核,實際上就只有一行代碼:

eval(expression) = apply(operator,
      eval(expression1),
      eval(expression2)
)

表達式expression的求值結果,等於把算子作用於子表達式1和子表達式2的值。

我相信我能體會到約翰麥卡錫在60年前,第一次看到她時候的震撼。

表達式

前面講到,她是基於表達式求值的,也就是說每一行源代碼都會有一個求值結果。

作爲一個遞歸函數,肯定會有一個終止條件,求值的終止條件,在於某個子表達式是原子類型。

所以我們把表達式,細分成了原子表達式和複合表達式。

複合表達式如:(+ 1 2)或者如一個變量定義(define pi 3.14)。它的求值邏輯在前面已經講過了。

原子表達式如:數字1,求值結果依然是數字1,字符串foo,求值結果依然是字符串foo

上下文

剛纔介紹原子表達式的時候,我們忽略了一種情況,如果是變量,比如pi,應該怎麼辦?

實際上我們應該嘗試去獲取變量的定義,也就是嘗試從上下文(或者說環境變量)中取值。

那麼上下文又是什麼?

實際上你可以把它簡單的理解成一個Map
(在沒有函數調用的情況下,這樣的解釋總是說的通)。

每當對define相關的表達式進行求值時,比如(define pi 3.14),實際上,就是把這一個鍵值對設置進上下文中。

(還記得我們的apply函數嗎?這就是define關鍵字apply的結果,也可以說是define的語義。)

因此,當嘗試對於pi進行求值時,實際上是去Map里根據key去查value。

Lambda!

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