[英][Little Schemer Ch3 pp.34 & 37]: Why (rember a (cdr lat)) as the 2nd argument of cons interpreted as unknown on p.37 example
我使用DrRacket調試模式一步一步地在p.34和p.37上運行了兩個示例。 下面是兩個示例的第一次處理(cdr lat)
時的堆棧窗口結果。
p.34,沒有cons
的失敗示例
(define rember
(lambda (a lat)
(cond
((null? lat) '())
(else (cond
((eq? a (car lat)) (cdr lat))
(else (rember a (cdr lat)))
)))))
(rember 'c '(a b c d))
調試器中的堆棧區域:
(cdr…)
(……)
第37頁,最后一行有cons
:
(define rember
(lambda (a lat)
(cond
((null? lat) '())
(else (cond
((eq? a (car lat)) (cdr lat))
(else (cons (car lat)
(rember a (cdr lat)))))))))
(rember 'c '(a b c d))
調試器中的堆棧區域:
(cdr…)
(……)
(……)
與第37頁的代碼堆棧區表明,第二個電話rember
已被列為未知前處理(cdr lat)
2個示例的唯一區別是第37頁添加了“ cons
”。 Cons接受2個參數,一個s表達式和一個列表。
沒有(cdr lat)
, rember
本身不會返回列表。 本書前40頁中包含(cdr lat)
所有示例都具有相同的(function (cdr variable)
格式)。
我不明白為什么第37頁的示例rember
本身被識別為未知的並且有理由進行掛起的還原,同時可以處理包含的(cdr lat)
。
還是為什么以這種方式解釋cons
第二個論點的位置而rember
。
謝謝!
我強烈建議您在此處使用步進器,而不要使用調試器。 我認為您會看到一套更一致的削減規則。 具體來說,我認為您不會看到“識別為未知”的任何內容。
要使用步進器,請執行以下操作:打開一個新緩沖區,確保將語言級別設置為使用列表縮寫的初學者,然后將定義和調用粘貼到定義窗口中。 點擊“步驟”。 我認為您很快就會看到兩次評估之間的差異。
如果沒有任何意義,請詢問后續問題!
TL; DR:您所看到的(和錯誤解釋的)是函數調用的堆棧以及尾遞歸的影響。
要回答有關調試器的特定問題:您的解釋是錯誤的。 您看到的是函數調用的運行時堆棧 ,這些堆棧使您到達執行時間軸上的特定位置 。
這不是 “未知”, 也不是“稍后減少”。 您已經遍歷了當前的執行點。 它是什么,正在等待嵌套調用的結果,以繼續對結果進行處理 。
如果再單擊“ Step”幾次(使用第37頁代碼),您將到達更深的地方,在“ 堆棧”區域中將看到更多(rember)
。 您當前的執行點顯示在堆棧的最上方; 最早–最底層。
注意,“ 變量” 區域顯示了該特定調用框架的變量值。
如果將鼠標光標移到較低的位置(rember)
並單擊它,您將看到其變量的值:
Racket的調試器已經習慣了一些。
還要注意, 左上角的“最后評估值”字段以很小的字母顯示 (在上圖中)。 這是調試時非常重要和有用的信息。 它可以使用是一點點在屏幕上更為明顯。
您看不到(rember)
的堆棧隨第一個代碼一起增長的原因(p.34),
就是它是尾遞歸的 。 深度嵌套調用rember
的結果無濟於事,除非將其進一步返回。 因此無需為此保存任何狀態。 這意味着rember
的調用框架將被重用,替換,這就是為什么您只能在Stack底部看到其中一個的原因。
但與第36頁的代碼有更多的東西要與返回值來完成-一個前面的列表元素必須是cons
編到的結果。 這意味着必須保留列表元素,並將其記住在某個地方。 某處是rember
的調用幀,其中該列表元件作為訪問(car lat)
中,出於該值lat
,在執行時間線點。
同樣,對於具有(else (function (cdr ...
pattern)的所有其他函數,這也意味着它們也是尾遞歸的 。但是,如果您看到類似(else (cons ... (function (cdr ...
,那么他們是不是該cons
是在路上。
為了更好地了解發生了什么,我們用等式模式匹配偽代碼重寫它:
(rember34 a lat) =
{ (null? lat) -> '()
; (eq? a (car lat)) -> (cdr lat)
; else -> (rember a (cdr lat))
}
這進一步簡化為三個子句,
rember34 a [] = []
rember34 a [a, ...as] = as
rember34 a [b, ...as] = rember a as
在沒有明確說明的情況下,僅在視覺上就可以理解此偽代碼嗎? 我希望是這樣。 另一個定義是
rember37 a [] = []
rember37 a [a, ...as] = as
rember37 a [b, ...as] = [b, ...rember a as]
現在,僅通過查看這些定義,我們就可以看到差異以及每個定義的作用。
第一, rember34
,沿着這樣的例子不勝枚舉(這是它的第二個參數), (3rd clause)
,直到它找到a
在它(第一個參數),如果確實如此(2nd clause)
,它返回列表中的其余部分那一點 。 如果沒有a
找到(3rd clause)
,我們已經到達了列表的末尾(1st clause)
,使列表以繼續沿着現在是空的( []
空列表[]
返回(1st clause)
。
說得通。 例如,
rember34 3 [1,2,3,4,5] % Tail-Recursive Call:
= rember34 3 [2,3,4,5] % Just Returning The Result...
= rember34 3 [3,4,5] % Call Frame Is Reused.
= [4,5]
rember34 3 [1,2]
= rember34 3 [2]
= rember34 3 []
= []
第二個rember37
功能相同,但有一個關鍵的區別:將每個不匹配的元素保留在找到和刪除的元素之前(與以前一樣)。 這意味着,如果找不到此類元素,則將重新創建相同的列表。 例如,
rember37 3 [1,2,3,4,5]
= [1, ...rember37 3 [2,3,4,5]] % the->
=> [2, ...rember37 3 [3,4,5]] % stack->
<= [4,5] % grows
<= [2,4,5] % <-and
= [1,2,4,5] % <-contracts
rember37 3 [1,2]
= [1, ...rember37 3 [2]] % must remember 1,
=> [2, ...rember37 3 []] % and 2, to be used
<= [] % after the recursive call
<= [2] % has returned its result
= [1,2] % to its caller
希望這可以澄清事情。
旁注:在尾遞歸模態cons
優化下,
rember37 3 [1,2,3,4,5]
= [1, ...rember37 3 [2,3,4,5]]
= [1, ...[2, ...rember37 3 [3,4,5]]]
= [1,2, ...rember37 3 [3,4,5]]
= [1,2, ...[4,5]]
= [1,2,4,5]
rember37 3 [1,2]
= [1, ...rember37 3 [2]]
= [1, ...[2, ...rember37 3 []]]
= [1,2, ...rember37 3 []]
= [1,2, ...[]]
= [1,2]
這也很容易被懶惰的評估!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.