为什么这会返回列表'(5)而不是数字5?

时间:2013-03-30 19:41:31

标签: 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 alist)))))

当我在1到5的整数列表上调用它时,我得到输出'(5):

> (last-pair (list 1 2 3 4 5))
'(5)

我期待5,就像(car (list 1 2 3 4 5))返回1而不是'(1)的方式一样。

为什么我会'(5)而不是5


我正在使用DrRacket 5.3.3和Racket Scheme。

编辑1:MIT-Scheme似乎没有这样做。 last-pair返回5而不是'(5)。哪个是对的?!?

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

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

< 用一些关于缩进差异的代码剪掉了流行的部分;现在的问题是定义过程的位置,请参阅编辑4 >

编辑4:好的我已经简化了问题。

  • 在MIT-Scheme中,'(5)返回(last-pair (list 1 2 3 4 5)),其中5定义如上。无论缩进。
  • 在DrRacket中,当在定义窗口中定义last-pair过程,然后单击“运行”时,last-pair将返回(last-pair (list 1 2 3 4 5))。无论缩进。
  • 在DrRacket中,当在接口窗口(REPL)中定义5过程时,last-pair'(5)。无论缩进。

这是一个截图: Racket gives different results for same function defined in different windows

2 个答案:

答案 0 :(得分:3)

由于(list 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

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

简而言之:混淆是由于在交互窗口中重新定义了内置函数。在定义窗口中按预期处理重新定义。虽然令人困惑但是交互窗口的行为方式背后有原因(解决这个问题,反过来又会引起其他地方的混乱)。

答案 1 :(得分:0)

最好不要使用内置名称last-pair。我建议使用更符合您期望的内容,例如last-elem

重命名功能时,请务必重命名;即,不仅在定义站点更改函数的名称,而且在每个调用站点更改函数的名称,当然包括在其正文中,它被称为递归。必须勤勉地重命名,否则很容易引入新的错误。


关于REPL的奇怪行为。我的猜测是,当你进入

(define (last-pair alist)             ;; definition
  (cond ((null? (cdr alist))
         (car alist))        
        (else
         (last-pair (cdr alist)))))   ;; call 

在REPL中,呼叫站点的last-pair仍然引用“外部”环境中的内置定义,因此此调用不是递归。如果是这种情况,REPL确实重新定义了内置函数,只是调用不是递归的。

我希望使用显式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翻译成简单的lambda形式是一回事;在明确的letrec内搞乱是另一回事。


如果这确实有效,那就意味着Racket REPL将简单定义(如(define (f x) ...body...))转换为简单lambda形式,(define f (lambda(x) ...body...)),而不是letrec形式,{{ 1}}。而且,Racket REPL中的(define f (letrec ((f (lambda(x) ...body...))) f))不会改变全局环境中的旧绑定,但会在旧的阴影旧绑定之上添加一个新绑定。< / p>

这表明另一种在REPL“修复它”的方法 - define

set!