1

我正在通过 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)无论缩进如何,它都会返回'(5).

<用一些关于缩进差异的代码剪断了前面的部分;现在的问题是定义过程的地方,请参阅编辑 4 >

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

  • 在 MIT-Scheme 中,(last-pair (list 1 2 3 4 5))返回5,其中last-pair定义如上。不管缩进。
  • 在 DrRacket 中,当last-pair程序在定义窗口中定义时,然后我单击“运行”,(last-pair (list 1 2 3 4 5))返回5. 不管缩进。
  • 在 DrRacket 中,当last-pair程序在界面窗口(REPL)中定义时,(last-pair (list 1 2 3 4 5)) returns'(5). 不管缩进。

这是一个屏幕截图: 对于在不同窗口中定义的相同功能,Racket 给出不同的结果

4

2 回答 2

3

由于(list 1 2 3 4 5)返回(cons 1 (cons 2 (cons 3 (cons 4 (cons 5 '())))))最后一对是(cons 5 '()).

In your function, chnage ((null? (cdr alist)) (car alist)) to ((null? (cdr alist)) alist) in order to retun the last pair (rather than the car of the last pair.

EDIT:

This explains the difference between the results you see in the definition and the interaction window. The main cause of the confusion is that last-pair is builtin. If you use the name my-last-pair you will see the same result in both windows.

In the definition window (define (last-pair ... is interpreted to mean that you want to redefine a builtin function. Therefore the last-pair refers recursively to you own definition of last-pair. This ultimately gives result 5 in your example

In the interaction window the recursive call to last-pair refers to the builtin version. So when last-pair is called with the list (2 3 4 5) the builtin version returns the last pair, which is (cons 5 '()) and this value is printed as (5).

In short: The confusion is due to redefining a builtin function in the interaction window. Redefinitions are handled as expected in the definition window. Although confusing there are reasons behind the way the interaction window behaves (fixing this problem, will in turn cause confusion elsewhere).

于 2013-03-30T20:15:29.887 回答
0

Better don't use the built-in name last-pair. I suggest using something more descriptive of what you expect, like last-elem for example.

When renaming your function, be sure to rename it throughout; i.e. change the name of the function not only at the definition site, but also at each call site, including of course inside its body, where it gets called recursively. Renaming must be performed diligently, it is very easy to introduce new errors otherwise.


About the strange behaviour at the REPL. My guess is, when you entered

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

at the REPL, the last-pair at the call site still referred to the built-in definition from the "outer" environment, so this call wasn't recursive. If that is the case, REPL did redefine the built-in, it's just that the call wasn't recursive.

I'd expect making an internal definition with explicit letrec should fix it, even when entered at the 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

because the first call now calls into the recursive internal version explicitly, being inside the letrec form. Or maybe it'll get screwed somehow too, but I'd be really surprised if it did. :) Translating defines without internal definitions as simple lambda forms is one thing; messing inside the explicit letrec is quite another.


If this indeed works it'll mean that the Racket REPL translates simple definitions like (define (f x) ...body...) as simple lambda forms, (define f (lambda(x) ...body...)), not as letrec forms, (define f (letrec ((f (lambda(x) ...body...))) f)). And also, that define at the Racket REPL does not alter the old binding in the global environment, but adds a new binding into it on top of the old, shadowing the old binding.

这提出了另一种在 REPL 中“修复它”的方法——使用set!

> (define f #f)
> (set! f (lambda(x) ...body...))  ; alter the old binding explicitly 
> (f x)
于 2013-04-01T09:58:56.880 回答