Scheme 为什么会返回一个列表';(5) 而不是数字5?

Scheme 为什么会返回一个列表';(5) 而不是数字5?,scheme,racket,sicp,mit-scheme,Scheme,Racket,Sicp,Mit Scheme,我正在处理SICP,我正在处理的练习要求一个过程返回列表中的最后一个元素。我实现了执行此操作的过程last pair,但我不明白为什么它返回的是列表而不是数字: (define (last-pair alist) (cond ((null? (cdr alist)) (car alist)) ; still happens if this is just "car alist)" (else (last-pair (cdr a

我正在处理SICP,我正在处理的练习要求一个过程返回列表中的最后一个元素。我实现了执行此操作的过程
last pair
,但我不明白为什么它返回的是列表而不是数字:

(define (last-pair alist)
  (cond ((null? (cdr alist))
         (car alist))        ; still happens if this is just "car alist)"
        (else
         (last-pair (cdr alist)))))
当我在从1到5的整数列表中调用它时,我得到了输出'(5):

我期望
5
,比如
(car(list 1 2 3 4 5))
将如何返回
1
而不是
”(1)

为什么我得到
”(5)
而不是
5


我正在使用DrRacket 5.3.3和Racket方案

编辑1:MIT Scheme似乎没有这样做
最后一对
返回
5
而不是
”(5)
。哪个是正确的

编辑2:有趣的是,在DrRacket(不在MIT方案中)中,如果第二行
(cond((null?(cdr-alist))
缩进两个空格,则调用过程时,它返回
”(5)
。但是,当第二行没有缩进时,它返回
5
。这是一个小故障吗?我相信方案口译员应该遵循的是括号,对吗

编辑3:我开始认为这是DrRacket中的一个小故障。当我将过程定义放在“定义”窗口(通常是顶部的编辑器窗格)中时,无论缩进如何,过程都将返回
5
。但是,如果我在界面窗口中定义它,缩进会影响编辑2中所述的结果。(编辑4)不管缩进是什么,它都会返回
”(5)

编辑4:好的,我简化了这个问题

  • 在MIT方案中,
    (最后一对(列表1 2 3 4 5))
    返回
    5
    ,其中
    最后一对
    是在上面定义的。不考虑缩进
  • 在DrRacket中,当定义窗口中定义了
    最后一对
    过程,然后我单击“运行”时,
    (最后一对(列表1 2 3 4 5))
    返回
    5
    ,而不考虑缩进
  • 在DrRacket中,当界面窗口(REPL)中定义了
    最后一对
    过程时,
    (最后一对(列表1 2 3 4 5))返回
    ”(5)。不考虑缩进
以下是一个屏幕截图: 因为
(列表1 2 3 4 5)
返回
(cons 1(cons 2(cons 3)(cons 4)(cons 5’(щщ))
最后一对是
(cons 5’())

在函数中,将chnage
((null?(cdr-alist))(car-alist))
更改为
((null?(cdr-alist))alist)
,以便重新运行最后一对(而不是最后一对的汽车)

编辑:

这解释了您在定义和交互窗口中看到的结果之间的差异。造成混淆的主要原因是
last pair
是内置的。如果使用名称
my last pair
,您将在两个窗口中看到相同的结果

在定义窗口中,
(define(last pair…
)被解释为意味着您希望重新定义一个内置函数。因此,
last pair
递归地引用您自己对
last pair
的定义。在您的示例中,这最终给出了结果5

在交互窗口中,对
最后一对
的递归调用指的是内置版本。因此,当使用列表
(2 3 4 5)
调用
最后一对
时,内置版本返回最后一对,即
(cons 5'())
,该值打印为
(5)


简言之:混乱是由于在交互窗口中重新定义了内置函数。重新定义在定义窗口中按预期进行处理。尽管混乱,但交互窗口的行为方式背后有一些原因(解决此问题反过来会在其他地方造成混乱).

最好不要使用内置名称
最后一对
。我建议使用更具描述性的名称,例如
最后一个元素

重命名函数时,请确保始终对其进行重命名;也就是说,不仅在定义站点更改函数名称,而且在每个调用站点更改函数名称,当然包括在递归调用函数的函数体内部。重命名必须认真执行,否则很容易引入新错误


我猜你进来的时候

(define (last-pair alist)             ;; definition
  (cond ((null? (cdr alist))
         (car alist))        
        (else
         (last-pair (cdr alist)))))   ;; call 
在REPL中,调用站点上的最后一对
仍然引用了“外部”环境中的内置定义,因此此调用不是递归的。如果是这种情况,REPL确实重新定义了内置定义,只是调用不是递归的

我希望使用explicit
letrec
创建一个内部定义应该可以修复它,即使在REPL中输入:

(define (last-pair alist)
  (letrec ((last-pair (lambda (alist)          ;; internal definition
             (cond ((null? (cdr alist))
                (car alist))        
              (else
                (last-pair (cdr alist)))))))   ;; recursive call
     (last-pair alist)))                       ;; first call
因为第一个调用现在显式地调用递归内部版本,在
letrec
表单中。或者它也会被搞砸,但如果它真的被搞砸了,我会非常惊讶的。:)将没有内部定义的
define
s转换为简单的
lambda
表单是一回事;在显式
letrec
中弄乱是另一回事


如果这真的有效,这将意味着Racket REPL将简单的定义,如
(定义(f x)…body…
转换为简单的
lambda
形式,
(定义f(lambda(x)…body…)
,而不是
letrec
形式,
(定义f(lambda(x)…body…)形式)
。而且,Racket REPL上的
定义
不会改变全局环境中的旧绑定,而是在旧绑定的基础上添加一个新绑定,以隐藏旧绑定

这表明了在REPL上“修复它”的另一种方法-设置

> (define f #f)
> (set! f (lambda(x) ...body...))  ; alter the old binding explicitly 
> (f x)

我不认为这是
list
返回的结果,t
> (define f #f)
> (set! f (lambda(x) ...body...))  ; alter the old binding explicitly 
> (f x)