问题
I am working through SICP, and the exercise I am working on asks for a procedure that returns the last element in a list. I implemented the procedure last-pair
to do this, but I'm confused why it's returning a list rather than a number:
(define (last-pair alist)
(cond ((null? (cdr alist))
(car alist)) ; still happens if this is just "car alist)"
(else
(last-pair (cdr alist)))))
When I invoke it on a list of the integers from 1 to 5, I get the output '(5):
> (last-pair (list 1 2 3 4 5))
'(5)
I was expecting 5
, like how (car (list 1 2 3 4 5))
would return 1
not '(1)
.
Why do I get '(5)
and not 5
?
I'm using DrRacket 5.3.3 and Racket Scheme.
EDIT 1: MIT-Scheme does not appear to do this. last-pair
returns 5
not '(5)
. Which is correct?!?
EDIT 2: Interestingly, in DrRacket (not in MIT-Scheme), if the second line (cond ((null? (cdr alist))
is indented two spaces, when the procedure is called, it returns '(5)
. But, when the second line is not indented, it returns 5
. Is this a glitch? I believe all that Scheme interpreters are supposed to follow is parentheses, correct?
EDIT 3: I am beginning to think this is a glitch in DrRacket. When I place the procedure definition in the definitions window (typically the top editor pane), regardless of indentation, the procedure will return 5
. But, if I define it in the interface window, the indentation affects the result as described in Edit 2. (EDIT 4) regardless of the indentation also, it will return '(5)
.
< snipped prevous part with some code about differences in indentation; the problem now is just where the procedure is defined, see Edit 4 >
EDIT 4: Ok I have simplified the problem.
- In MIT-Scheme,
(last-pair (list 1 2 3 4 5))
returns5
, wherelast-pair
is defined above. Regardless of indentation. - In DrRacket, when the
last-pair
procedure is defined in the definitions window, and then I click "Run",(last-pair (list 1 2 3 4 5))
returns5
. Regardless of indentation. - In DrRacket, when the
last-pair
procedure is defined in the interface window (the REPL),(last-pair (list 1 2 3 4 5)) returns
'(5). Regardless of indentation.
Here's a screenshot:
回答1:
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 define
s 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.
This suggests yet another way to "fix it" at the REPL — with set!
:
> (define f #f)
> (set! f (lambda(x) ...body...)) ; alter the old binding explicitly
> (f x)
回答2:
Since (list 1 2 3 4 5)
returns (cons 1 (cons 2 (cons 3 (cons 4 (cons 5 '())))))
the last pair is (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).
来源:https://stackoverflow.com/questions/15722726/why-does-this-return-a-list-5-rather-than-the-number-5