Debugging [Little Schemer Ch3第34和37页]:为什么(请记住a(cdr lat))作为cons的第二个参数在第37页示例中被解释为未知

Debugging [Little Schemer Ch3第34和37页]:为什么(请记住a(cdr lat))作为cons的第二个参数在第37页示例中被解释为未知,debugging,recursion,racket,tail-recursion,the-little-schemer,Debugging,Recursion,Racket,Tail Recursion,The Little Schemer,我使用DrRacket调试模式在p.34和p.37上一步一步地运行这两个示例。下面是两个示例中第一次处理cdr lat时的堆栈窗口结果 p、 34,没有缺点的失败例子 调试器中的堆栈区域: cdr… 记住 p、 37最后一行有cons: (define rember (lambda (a lat) (cond ((null? lat) '()) (else (cond ((eq? a (car lat)) (cdr lat))

我使用DrRacket调试模式在p.34和p.37上一步一步地运行这两个示例。下面是两个示例中第一次处理cdr lat时的堆栈窗口结果

p、 34,没有缺点的失败例子

调试器中的堆栈区域:

cdr… 记住

p、 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… 记住… 记住

带有p.37代码的堆栈区域表明,在处理cdr lat之前,rember的第二次调用已被分类为未知

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

如果没有cdr lat,rember本身不会返回列表。本书前40页中包含cdr lat的所有示例都具有相同的函数cdr变量格式

我不明白为什么p.37示例rember本身被标识为未知,并且在可以处理所包含的cdr lat的情况下,有理由等待减少

或者为什么要记住,在第二个论点的立场上,反对党被这样解释

谢谢

我强烈建议您在这里使用步进程序,而不是调试器。我想你会看到一套更加一致的简化规则。具体来说,我认为你不会看到任何被认定为未知的东西

要使用stepper:打开一个新的缓冲区,确保语言级别设置为带列表缩写的初学者,并将定义和调用粘贴到定义窗口中。单击步骤。我想你会很快看到这两个评估之间的差异

如果有任何不合理的地方,请提出后续问题

我强烈建议您在这里使用步进程序,而不是调试器。我想你会看到一套更加一致的简化规则。具体来说,我认为你不会看到任何被认定为未知的东西

要使用stepper:打开一个新的缓冲区,确保语言级别设置为带列表缩写的初学者,并将定义和调用粘贴到定义窗口中。单击步骤。我想你会很快看到这两个评估之间的差异

如果有任何不合理的地方,请提出后续问题

TL;DR:你所看到和误解的是函数调用的堆栈,以及尾部递归的影响

回答关于调试器的特定问题:您的解释是错误的。您看到的是函数调用的运行时堆栈,它将您带到了执行时间线中您现在所在的特定点

这不是未知的,不是以后要减少的。你已经经历了它,到了你现在的执行点。实际上,它正在等待嵌套调用的结果,以继续处理结果

如果你用p.37代码再点击几次步骤,你会到达一个更深的点,你会看到更多的记忆出现在堆栈区域。当前执行点显示在堆栈的最顶端;最早的–最底层的

请注意,变量区域显示了特定调用框架的变量值

如果移动鼠标光标并将鼠标悬停在较低的记忆上并单击它,您将看到其变量的值:

Racket的调试器有点习惯了

另外,请注意上一张图中左上角的最后一个评估值字段,该字段以非常小的字母显示。这是调试时非常重要和有用的信息。它可能需要在屏幕上更清晰一点

你看不到记忆堆栈随着第一个代码p.34而增长的原因

就是这样。对于rember的深层嵌套调用的结果,除了将其返回到更远的位置之外,没有什么可以做的;因此,没有必要为此保存任何状态。这意味着rember的调用框架将被重用和替换,这就是为什么您只能在堆栈底部看到其中一个

但是对于p.37的代码,需要对返回值做更多的事情——必须在结果上使用前面的列表元素。这意味着列表元素必须被保留,并在某处被记住。这是rember的调用框架,在执行时间线的那个点上,列表元素被访问为car lat,用于lat的值

类似地,对于具有else函数cdr的所有其他函数。。。模式,这意味着它们也是尾部递归的。但是如果你看到其他类似的东西。。。函数cdr…,则它们不是。犯人挡道了

为了更好地了解发生了什么,我们在等式模式匹配伪代码中重写它:

伦伯拉= {null?lat->' ;eq?a汽车横向->cdr横向 ;否则->记住cdr lat } 这进一步 简化为三条,

伦贝尔34 a[]=[] rember34 a[a,…as]=as rember34 a[b,…as]=rember a as 这段伪代码是否足够直观易懂,而无需明确解释?我希望是这样。另一个定义是

伦贝尔37 a[]=[] rember37 a[a,…as]=as rember37 a[b,…as]=[b,…rember a as] 现在只要看看这些定义,我们就能看到它们的区别,以及它们各自的作用

第一个,rember34,沿着它的第二个参数,第三个子句的列表,直到它在它的第一个参数中找到一个,如果它找到了第二个子句,它将在该点返回列表的其余部分。如果没有找到第3个子句,并且我们已经到达列表第1个子句的末尾,因此要继续的列表现在是空的[],那么空的列表[]将返回第1个子句

有道理。比如说,

rember34 3[1,2,3,4,5]%尾部递归调用: =rember34 3[2,3,4,5]%仅返回结果。。。 =rember34 3[3,4,5]%调用帧被重用。 = [4,5] rember34 3[1,2] =伦贝34 3[2] =伦贝34 3[] = [] 第二个是rember37,它的作用是相同的,但有一个关键的区别:它将每个不匹配的元素保持在它找到并删除的元素之前,就像以前一样。这意味着如果没有找到这样的元素,将重新创建相同的列表。比如说,

Rember373[1,2,3,4,5] =[1,…Rember373[2,3,4,5]% =>[2,…rember37 3[3,4,5]%stack-> TL;DR:你所看到和误解的是函数调用的堆栈,以及尾部递归的影响

回答关于调试器的特定问题:您的解释是错误的。您看到的是函数调用的运行时堆栈,它将您带到了执行时间线中您现在所在的特定点

这不是未知的,不是以后要减少的。你已经经历了它,到了你现在的执行点。实际上,它正在等待嵌套调用的结果,以继续处理结果

如果你用p.37代码再点击几次步骤,你会到达一个更深的点,你会看到更多的记忆出现在堆栈区域。当前执行点显示在堆栈的最顶端;最早的–最底层的

请注意,变量区域显示了特定调用框架的变量值

如果移动鼠标光标并将鼠标悬停在较低的记忆上并单击它,您将看到其变量的值:

Racket的调试器有点习惯了

另外,请注意上一张图中左上角的最后一个评估值字段,该字段以非常小的字母显示。这是调试时非常重要和有用的信息。它可能需要在屏幕上更清晰一点

你看不到记忆堆栈随着第一个代码p.34而增长的原因

就是这样。对于rember的深层嵌套调用的结果,除了将其返回到更远的位置之外,没有什么可以做的;因此,没有必要为此保存任何状态。这意味着rember的调用框架将被重用和替换,这就是为什么您只能在堆栈底部看到其中一个

但是对于p.37的代码,需要对返回值做更多的事情——必须在结果上使用前面的列表元素。这意味着列表元素必须被保留,并在某处被记住。这是rember的调用框架,在执行时间线的那个点上,列表元素被访问为car lat,用于lat的值

类似地,对于具有else函数cdr的所有其他函数。。。模式,这意味着它们也是尾部递归的。但是如果你看到其他类似的东西。。。函数cdr…,则它们不是。犯人挡道了

为了更好地了解发生了什么,我们在等式模式匹配伪代码中重写它:

伦伯拉= {null?lat->' ;eq?a汽车横向->cdr横向 ;否则->记住cdr lat } 这进一步简化为三条条款

伦贝尔34 a[]=[] rember34 a[a,…as]=as rember34 a[b,…as]=rember a as 这段伪代码是否足够直观易懂,而无需明确解释?我希望是这样。另一个定义是

伦贝尔37 a[]=[] rember37 a[a,…as]=as rember37 a[b,…as]=[b,…rember a as] 现在只要看看这些定义,我们就能看到它们的区别,以及它们各自的作用

第一个,rember34,沿着它的第二个参数,第三个子句的列表,直到它在它的第一个参数中找到一个,如果它找到了第二个子句,它将在该点返回列表的其余部分。如果没有找到第3条,并且我们已经到达列表第1条的末尾,那么要继续的列表现在是空的[ ],则返回空列表[]作为第1子句

有道理。比如说,

rember34 3[1,2,3,4,5]%尾部递归调用: =rember34 3[2,3,4,5]%仅返回结果。。。 =rember34 3[3,4,5]%调用帧被重用。 = [4,5] rember34 3[1,2] =伦贝34 3[2] =伦贝34 3[] = [] 第二个是rember37,它的作用是相同的,但有一个关键的区别:它将每个不匹配的元素保持在它找到并删除的元素之前,就像以前一样。这意味着如果没有找到这样的元素,将重新创建相同的列表。比如说,

Rember373[1,2,3,4,5] =[1,…Rember373[2,3,4,5]% =>[2,…rember37 3[3,4,5]%stack->

OP误解了堆栈显示。这种差异是由尾部/非尾部递归造成的。我已经更新了我的答案,甚至还有关于TR的图片和俳句。。。。在这一点上没有人会看到它;哦,好吧,我试过初级和中级,都很棒。然而,这些学习版本的Racket似乎缺少一些基本元素。至少我注意到了一双?未定义OOTB。所以我使用列表?用于定义原子?你应该能使用cons?代替成对的?。还有其他的吗?球拍文档应该有这个列表。我刚才看到了。我不知道有犯人?今晚我试试!操作错误地解释了堆栈显示。这种差异是由尾部/非尾部递归造成的。我已经更新了我的答案,甚至还有关于TR的图片和俳句。。。。在这一点上没有人会看到它;哦,好吧,我试过初级和中级,都很棒。然而,这些学习版本的Racket似乎缺少一些基本元素。至少我注意到了一双?未定义OOTB。所以我使用列表?用于定义原子?你应该能使用cons?代替成对的?。还有其他的吗?球拍文档应该有这个列表。我刚才看到了。我不知道有犯人?今晚我试试!ThxThx!现在对第37页有了自己的解释。如果我是对的,请告诉我。除了调用rember变量外,还有两组过程——“Before else”和“else”。只有在“Before else”部分得到结果之后才能执行“else”部分。该部分不涉及调用变量。变量的第二次调用在p34和p37示例中是相同的,只传递“在其他事情之前”过程。两个示例之间的唯一区别是p34的其他部分过程只是变量的第二次调用。虽然p37示例的其他部分有一个独立的过程,但只有一个过程。通常,当我们达到cons car lat rember a cdr lat时,我们需要评估cons a B表格。a的值很容易找到。要找到B的值,我们发现C=cdr lat的值也很容易找到,走到一边,根据相同的规则找到B=rember C的值。现在我们已经计算了B,我们继续我们的R=cons A B任务。现在我们有了A和B,计算R也很容易。我理解你说的话,非常好。我的意思是数据观点。lat的值仅在null?>Eq?>Cdr latrember之间循环。第二个进程cons…是有条件的,只有在Eq?=t得到非null的结果时,才会在第一个进程之后执行。TLS是我学习编程的第一步。这确实是一个挑战,但我知道我正在一页一页地取得进展!再次感谢h、 好的。然后不循环,在cond的两个子句之间切换;通过偶数。这就是cond的工作方式,进程一个接一个地依次通过它的子句,直到其条件返回一个真值的子句-然后,输入这个子句。碰巧它包含一个递归调用,这导致我们的进程再次调用再次检查这两个子句,尝试它们的条件-但是在一个新的、更深层次的递归上。不客气,很愉快!然后不循环,切换,正如你所看到的,像我这样的初学者经常甚至不能正确地问这个问题。原子的标准代码?is和not pair?atom not null?atom。而不是true或false。是吗无法重写此函数并给出结果“不是原子而是一个列表”或“不是原子而是一个空列表”或“这是一个原子”?Thx!现在对p37有了自己的解释。请告诉我是否正确。除了调用rember变量外,还有两组进程-“Before-else”和“else”;“else”部分只能在之后执行“before else部分获得结果。此部分不涉及调用变量。变量的第二次调用在p34和p37示例中是相同的,只传递”before else“的值。”过程。两个示例之间的唯一区别是p34的其他部分过程只是变量的第二次调用。虽然p37示例的其他部分有一个独立的过程,但只有一个过程。通常,当我们达到cons car lat rember a cdr lat时,我们需要评估cons a B表格。a的值很容易确定
发现为了找到B的值,我们找到了C=cdr lat的值,也很容易找到,转到旁边,根据相同的规则找到B=rember C的值。现在我们已经计算了B,我们继续执行R=cons A B任务。现在我们有了A和B,计算R也很容易。我理解你所说的话,还有伟大的thx。我的意思是数据流的观点。lat的值仅在null之间循环?>情商?>拉特伦伯指挥官。第二道工序是。。。是有条件的,并且只有在等式=t获取的结果不是null。TLS是我学习编程的第一步。这确实是一个挑战,但我知道我正在一页一页地进步!再见,好的。然后不循环,在cond的两个条款之间切换;经历了平局。cond就是这样工作的,进程一个接一个地遍历它的子句,直到其条件返回真值的子句,然后,输入这个子句。碰巧它包含了一个递归调用,这会导致我们的进程再次通过这两个子句,再次尝试它们的条件——但是在一个新的、更深层次的递归上。不客气,祝你快乐!你可以看到,像我这样的初学者常常连问题都问不好。原子的标准代码?是不是一对?原子不为空?原子而不是真或假。是否可以重写此函数并给出“不是原子而是列表”或“不是原子而是空列表”或“这是原子”的结果?
(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))