圖片皆摘錄於 https://www.comp.nus.edu.sg/~cs1101s/sicp/
經過3.1賦值之後,光用“名字”對應值已經不再適用了,需要一個“位置”去儲存這些值,這些“位置”會維持在一個結構,稱為“environments”
environments 就是一個 “frame”的序列,而每個 frame 是一個 “binding”的表格(可能為空),binding將變量與值對應起來,一個frame中的binding最多只會對應一個變量,如果變量在任何frame中都沒有定義binding,就稱為“unbound”。
Figure 3.1 有三個 frame I, II, III,觀察x,在每個frame裡面的值 I: 3, II:7, III: 3
可看到 x 在 frame III中並沒有 binding, ,所以沿著D回去找發現 frame I有x的binding,因此x在frame II中的值與frame I中相同。
其實跟1.1.3說的是一樣的事情,但這次用environment model的求值方式,不一樣的是,會將compound function帶入argument。
在environment model中,一個function是一個pair,一邊指向程式碼與指向environment的pointer所組成,只有一個方法能創造function: by evaluating a function definition expression。
funciton square(x) {
return x * x;
}
其實是以下方式的syntactic sugar:
const square = x = x * x;
當對square(5)求值
創造新的Environment E1,且有x變數在初始的frame中,bound to 5,pointer指向global environment.
square在E1對body進行求值,回傳xx,x值已綁定5,55=25。
function square(x) {
return x * x;
}
function sum_of_squares(x, y) {
return square(x) + square(y);
}
function f(a) {
return sum_of_squares(a + 1, a * 2);
}
其實就是上一個段落的複雜版,只要記得對一個function求值的規則就對了!另一個有趣的地方就是,square(x) + square(y)其實是創造了兩個environment各自有自己的binding。
利用Environment來分析3.1.1的 “withdrawal processor”
function make_withdraw(balance) {
function withdraw(amount) {
if (balance >= amount) {
balance = balance - amount;
return balance;
} else {
return "Insufficient funds";
}
}
return withdraw;
}
在加入
const w1 = make_withdraw(100);
在對其求值w1(50);
以environment model來解析:
從 const w1 = make_withdraw(100);
開始就非常有趣了
首先一樣新建一個E1環境且存在一個參數balance綁定100,接著對make_withdraw求值,產生了一個新的function object,且被w1綁定在global環境中。
求值 w1(50);
同樣新建了一個環境,包含amount bound to 50,但這個環境的pointer是指向E1而不是global環境,而在這個環境中對function的body求值,求值後如下圖
求值後由於function body含有一個assignment balance = balance — amount ,執行後改變了E1中的 balance 變成50,且function object w1依然指向E1,原先的amout binding的frame已經不復存在,下次再求值w1,就又會產生新的frame、binding amount to 新的值,且指向E1。
如果再定義新的 w2 = make_withdraw(100);
呢?
將產生一個新的environment E2,其綁定著balance = 100,w2的指針一面指向E2、一面指向與w1相同的function。
最好玩的部分就是寫習題惹~
手畫了一個,有點醜XD
function abs(x) {
return x >= 0 ? x : -x;
}
function square(x) {
return x * x;
}
function average(x,y) {
return (x + y) / 2;
}
function sqrt(x) {
function good_enough(guess,x) {
return abs(square(guess) - x) < 0.001;
}
function improve(guess) {
return average(guess,x/guess);
}
function sqrt_iter(guess){
if (good_enough(guess,x)) {
return guess;
} else {
return sqrt_iter(improve(guess));
}
}
return sqrt_iter(1.0);
}
sqrt(5);
當 sqrt(2) 執行,創造了一個E1環境,當中的binding 有x 及一些function object,接著對 sqrt_iter(1.0) 求值,創造了E2環境,接著對 good_enough 求值,又創造了E3環境,雖然 sqrt_iter 與 good_enough 的參數都叫做guess ,但其實是創造在不一樣的環境裡的不同變數。
總結來說environment model提供了一些技巧:
function內部的名稱不會與外部環境名稱相互干擾
function 內部名稱可以直接使用外部環境的,只要單純的將變數是free variables就好