簡體   English   中英

[Little Schemer Ch3 pp.34&37]:為什么(rember a(cdr lat))作為cons的第二個參數在p.37示例中解釋為未知

[英][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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM