一開始的時候,我只是希望她能遞歸的執行計算,比如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
,然後解析出+
、3
、4
這幾個元素,接着 把加法作用於3
和4
的值 。
爲了方便,我們可以讓輸入的格式是形如:(* (+ 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。