[Little Schemer Ch3 pp.34&37]:为什么在第37页的示例中,(rember a(cdr lat))作为cons的第二个参数解释为未知

虚假的

我使用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…)
(rember…)

第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…)
(rember…)
(rember…)

带第37页代码的堆栈区域表示在处理之前,第二个调用rember已被分类为未知(cdr lat)

2个示例的唯一区别是第37页添加了“ cons”。Cons有两个参数,一个s表达式和一个列表。

没有(cdr lat)rember它本身不会返回列表。(cdr lat)本书前40页中的所有示例都具有相同的(function (cdr variable)格式。

我不明白为什么p.37范例rember本身会被识别为未知数,并且有理由进行待处理的还原,同时(cdr lat)可以处理所包含的内容

还是为什么rember在那样cons解释的第二个论点的位置

谢谢!

内斯

TL; DR:您所看到的(和错误解释的)是函数调用堆栈以及尾递归的影响


要回答有关调试器的特定问题:您的解释是错误的。您看到的是功能调用运行时堆栈,这些堆栈使您到达执行时间轴上的特定位置

不是“未知”,也不是“稍后减少”。您已经完成了它,到达了当前执行点。它是什么,正在等待嵌套调用的结果,以继续对结果进行处理

如果再单击“ Step”几次(使用第37页代码),您将到达更深的地方(rember)Stack区域中将看到更多s出现您当前的执行点出现在堆栈的最上方最早–最底层。

在此处输入图片说明

注意,“变量”区域显示了该特定调用框架的变量值。

如果将鼠标光标移到下部(rember),然后单击它,您将看到变量的值:

在此处输入图片说明

Racket的调试器已经习惯了一些。

Also do notice the "last evaluated value" field in the top left corner shown in very small letters (in the previous image). This is a very important and useful piece of information, while debugging. It could use being a little bit more visible on the screen.

The reason you don't see the stack of (rember)s grow with the first code (p.34),

在此处输入图片说明

is that it is tail recursive. There is nothing to be done with the result of the deep nested invocation of rember except to return it further back; so there's no need to save any state for that. This means the invocation frame for rember gets reused, replaced, and that's why you only ever see one of them, at the bottom of the Stack.

But with the p.37's code there's more stuff to be done with the returned value - a preceding list element must be consed onto the result. This means that list element must be preserved, remembered somewhere. That somewhere is rember's invocation frame where that list element was accessed as (car lat), for that value of lat, at that point in execution timeline.

Similarly, for all the other functions that have (else (function (cdr ... pattern, this means they are tail-recursive too. But if you see something like (else (cons ... (function (cdr ..., then they are not. The cons is in the way.


To better see what's going on, we rewrite it in an equational pattern-matching pseudocode:

(rember34 a lat) =
    { (null? lat)        ->  '() 
    ; (eq? a (car lat))  ->  (cdr lat)
    ; else               ->  (rember a (cdr lat))
    }

This further simplifies to three clauses,

rember34 a []          =  []
rember34 a [a, ...as]  =  as
rember34 a [b, ...as]  =  rember a as

Is this pseudocode understandable enough just visually, without being explicitly explained? I hope that it is. The other definition is

rember37 a []          =  [] 
rember37 a [a, ...as]  =  as
rember37 a [b, ...as]  =  [b, ...rember a as]

Now just by looking at these definitions we can see the difference, and what each one does.

The first, rember34, goes along the list (which is its second argument), (3rd clause) until it finds a in it (its first argument), and if it does (2nd clause), it returns the rest of the list at that point. If there was no a found (3rd clause) and we've reached the end of the list (1st clause) so that the list to continue along is now empty ([]), the empty list [] is returned (1st clause).

Makes sense. For example,

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]

这很像它也将受到懒惰的评估!

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章