SICP 習題 1 6 解題總結

2021-09-01 19:15:44 字數 4897 閱讀 1929

sicp 習題 1.6 還是講的正則序和應用序,問題是從if過程的討論開始的,習題說到名叫alyssa p. hacker的人覺的不需要為if提供一種特殊形式,可以直接用常規過程呼叫cond來實現。

我第一次看到這道題的時候的完全不明白題目是什麼意思,我當時的反應是,「if有特殊形式嗎?」,我沒覺的if有什麼特殊呀。有這樣的反應是因為沒有認真思考習題1.5,這次做題目比較細緻,做習題1.5的時候就想過,使用正則序展開過程的時候,不理會if,直接展開所有過程不是更簡單一些嗎?後來發現,不理會if,直接展開過程是會導致問題的,必須對if進行特殊的處理才能讓直譯器正常工作。

我們先回想一下習題1.5,我們上次看習題1.5的時候就看到習題有乙個假設,就是不管是正則序還是應用序,都假設if過程對條件進行判斷後只對條件成立對應的部分進行處理,忽略條件不成立對應部分的部分。

有了這個假設,可以發現習題1.5中的過程在應用序環境下會「宕機」,而在正則序環境下會返回0。

而這種針對if過程的特殊處理就是if的特殊形式。

現在習題1.6問的問題就是針對if過程的這種特殊處理有必要嗎,通過常規的過程處理會有什麼問題。

解答這個問題的好方法就是建構乙個常規的過程去代替if過程,看會出現什麼問題。

習題種已經幫我們定義好了這個代替if的過程,叫做new-if,習題種講到這個過程是alyssa的朋友eva lu ator做的,過程定義如下:

(define (new-if predicate then-clause else-clause)

(cond (predicate then-clause)

(else else-clause)))

這個new-if過程在處理一般引數時是沒有問題的。做new-if過程的eva做了一下測試,都沒有問題:

(new-if (= 2 4) 0 5)

(new-if (= 2 2) 0 5)

接著,如果eva通過new-if來重寫求平方根的過程,似乎就有點問題了。

有關原來版本的求平方根的過程,想詳細了解的話需要回去看看1.1.7那節。1.1.7節講了使用牛頓法求平方根,裡面涉及的數學方法其實和本習題的目的沒有關係,不過看到牛頓法優美的地方真的是讓我這種沒有數學天分的人嘆為觀止。所以,建議大家還是好好理解一下1.1.7節的內容,一是感受一下大師的風範,二是後面的其它習題和這個還有關係。

總之,原來的過程是這樣的:

(define (sqrt-iter guess x)

(if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

其中good-enough?和improve過程的實現就不貼出來了,不清楚的話需要回去看看1.1.7節,不過這些細節和本習題關係不大。

一切執行正常,如果改為new-if的過程就是這樣的,很簡單,就是將if換成了new-if

(define (sqrt-iter guess x)

(new-if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

會出什麼問題呢?在mit-scheme裡跑一下以上**就知道了,mit-scheme又「宕機」了。

為什麼呢?來仔細看看,假設我們通過以下過程呼叫來測試,就是求16的平方更,從1開始猜,所以執行的**如下:

(sqrt-iter 1 16)

展開就是:

(new-if (good-enough? 1 16)

1 (sqrt-iter (improve 1 16)

16)))

因為scheme使用的是應用序,所以,對於過程new-if來講,系統會希望計算所有引數的值,然後代換到new-if過程中,需要計算的包括:

(good-enough? 1 16)

(sqrt-iter (improve 1 16) 16))

根據我們上面對於牛頓法的理解,我們知道(improve 1 16)會返回8.5,所以我們需要計算的其實是:

(sqrt-iter 8.5 16)

我們發現我們相當於回到了第一步計算(sqrt-iter 1 16)的時候,就是引數變了而已。我們需要繼續展開sqrt-iter過程。

這個過程會一直持續下去,不斷的遞迴呼叫。即使到我們的(good-enough? guess x)過程返回結果為真,以上過程還是會繼續。關鍵就是我們使用了new-if過程,而不是if過程。

其中的差別就是new-if是常規過程,會使用應用序計算所有引數,而if過程有特殊處理,會根據條件判斷的結果決定計算哪部分引數。

在lisp環境中,我們經常會說我們定義的過程和系統過程沒什麼差別,其實,在關鍵的地方,我們定義的過程和系統過程的差別是很大的,只是我們一般不需要關注這些差別而已。

有了以上的結果,我們可以進一步考慮一下在正則序環境中使用new-if會是什麼結果。

對於正則序的環境,它會將過程不斷展開,直到所有元素都是基本元素,然後對展開的結果進行歸約。

如果我們使用的是new-if,則以下部分會被展開:

(good-enough? guess x)

(sqrt-iter (improve guess x) x)

其中(improve guess x)可以直接展開,關鍵在於sqrt-iter過程,將sqrt-iter展開以後又是乙個new-if過程, 其中繼續包括下面兩部分需要展開:

(good-enough? guess x)

(sqrt-iter (improve guess x) x)

所以會形成乙個無限遞迴,不斷地展開,不會因為 good-enough?過程的返回值而停止。

也就是說,不管是正則序還是應用序,使用new-if去代替if都會發生問題無限遞迴的問題。

這時候,回想我們在習題1.5中討論的有關正則序的展開方式,我當時覺得理想的,優美的方式應該是對過程不斷展開,直到沒辦法展開為止,然後再進行歸約。但是這個理想的方式會導致大部分遞迴呼叫無法返回,因為大部分遞迴呼叫都需要乙個條件判斷來決定是否繼續進入遞迴過程,而不斷展開的方式會忽略條件判斷的結果,直接對過程的所有引數進行展開,從而導致問題。

所以,如果我們希望設計乙個可用的正則序環境,必須對if過程進行特殊的處理,就像習題1.5裡假設的一樣,不管是正則序還是應用序,if過程都是先對條件進行判斷,然後根據條件判斷的結果決定對if過程的哪部分進行進一步處理。

更進一步的思考是為什麼我們再平常使用的程式語言中不會遇到這樣的問題。

我們來看看c語言裡的if,大概是這樣子的:

if ( good_enough (guess,x) == true)

else

可以看出這裡關鍵是if不是乙個過程,不是乙個函式,不存在對所有if分支都進行計算的說法。我們從學習程式設計的那一天起就有乙個簡單的認識,如果if條件滿足,就執行這段**,如果條件不成立,就執行那段**,不會同時對條件的兩條分支都進行執行的。

也就是說,c語言中的if已經是經過特殊處理的,它不是乙個過程,所以不用擔心上面討論的導致無限遞迴的問題。

既然是這樣,接著就有乙個想法,如果我們在c語言裡強制將if做成乙個過程會有什麼樣的結果呢?

我們可以仿照習題1.6的方法,定義乙個new_if過程來代替if語句,new_if的定義如下:

double new_if ( int con_result, double yes_result, double no_result)

return no_result;

}

然後我們的sqrt_iter實現成這樣:

double sqrt_iter (double guess , double x)

執行一下sqrt_iter(1,16),

會發現sqrt_iter也會進入無限遞迴中。

也就是說,在c語言環境裡,如果將if實現為常規過程,很多遞迴呼叫會無法返回,這和lisp環境是一樣的。只是在c語言中,if天生就不是乙個過程,它被設計出來的時候就做了特殊處理了,所以我們在c語言中不需要擔心本習題討論的問題。

既然談到這裡,我們可以繼續問一下,c語言用的是應用序還是正則序?

從一般角度猜測,應該是應用序,因為應用序的效率比較高,問題是我們如何來驗證c語言是否使用應用序?

習題1.5的方法可以驗證lisp是使用的什麼求值方式,不過拿到c語言環境中這個方法就不行了,因為在c語言環境裡if不是乙個過程。

既然在c語言環境裡,我們就用c語言常用的方法,控制台輸出。

我們定義下面兩個過程:

int new_plus (int x)

int generate_x (int i)

然後執行以下**:

new_plus(generate_x(5));

我們要看generate_x(5)呼叫了幾次,如果是正則序,以上**應該被展開為:

return (generate_x(5)+generate_x(5));

這樣generate_x就被呼叫了兩次,應該在控制台裡看到兩句"generating x",事實上以上**只會在控制台輸出一行"generating_x",所以可以驗證c語言用的是應用序。

以上就是我對scip 習題1.6的總結啦。

SICP 習題 1 14 解題總結

sicp 習題 1.14要求計算出過程count change的增長階。count change是書中1.2.2節講解的用於計算零錢找換方案的過程。要解答習題1.14,首先你需要理解count change的工作方式,要理解count change的工作方式,最好是自己去實現一遍count chan...

SICP 習題 1 22 解題總結

sicp 習題 1.22 要求改進題中列舉出來檢查素數的過程,用來求1000,10000,100 000,還有1000 000附近的素數,然後比較求這些素數的時間,看是否符合 n 的複雜度。要完成這道題首先要將題目中列出的過程照抄到你的scheme環境中。因為書中的 使用了 runtime 過程,我...

SICP 習題 1 25 解題總結

sicp 習題 1.25 就是我上面說過的傷自尊的題了。習題1.25說到有個叫allyssa p.hacker的人說expmod過程完全沒有必要搞那麼麻煩,直接使用前面的fast expt過程和remainder過程就好了,她 叫alyssa的應該是女的吧 覺得可以這樣定義expmod define...