1

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)) returns 5, where last-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)) returns 5. 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: Racket gives different results for same function defined in different windows

kalaracey
  • 815
  • 1
  • 12
  • 28
  • Do you have a screenshot ? – soegaard Mar 30 '13 at 22:03
  • 1
    Can not replicate yet. Note that if you are entering definitions in the interactions, you may be running into a messy collision with the existing definition of `last-pair` defined in the Racket language: http://docs.racket-lang.org/reference/pairs.html?#%28def._%28%28lib._racket%2Flist..rkt%29._last-pair%29%29 It is much safer to keep all your definitions in the Definitions pane, and just treat the Interactions pane as a place to explore those definitions. – dyoo Mar 30 '13 at 23:18
  • Also note that the comment notation in Racket uses semicolons, not hashes. Does your real program have hashes? – dyoo Mar 30 '13 at 23:21
  • @dyoo no my real program does not; I have edited the question. – kalaracey Mar 30 '13 at 23:30
  • @soegaard see my screenshot. – kalaracey Mar 30 '13 at 23:46
  • 1
    you know, `last-pair` is a standard function name, and it returns a last pair, not a last element like you want it to. Maybe DrRacket refuses to redefine it, silently, when you enter the new definition at the REPL. Try the same definition with a different name, at the REPL, and tell us what happens. :) – Will Ness Mar 31 '13 at 00:10
  • @WillNess Nope, I redefined `last-pair` as `git-pair`, with exactly the same body, and the behavior is the same: if I define it in the defintitions Window, then click Run, then call it, I get `5`. But, if I define it in the REPL, then call it with the argument, I get `'(5)`. – kalaracey Mar 31 '13 at 20:53
  • this is very strange. did you reboot your computer? :) What if you call it `qqqzzz`? This is some weird artifact; it just shouldn't happen. – Will Ness Mar 31 '13 at 20:59
  • Did you remember to use git-pair in the body as well? – soegaard Mar 31 '13 at 21:02
  • What do you get when you type `(list 1)` at the REPL? Is it `'(1)` or is it `(1)`? – Will Ness Mar 31 '13 at 21:03
  • wait, with *exactly* the same body? **You should replace `last-pair` inside the body too, with the new name**. I propose `last-elem`: `(define (last-elem xs) (if (null? (cdr xs)) (car xs) (last-elem (cdr xs))))`. – Will Ness Mar 31 '13 at 21:49
  • @WillNess That was the problem. I replace the body word-for-word, including `last-pair`. When I changed that, `git-pair` worked fine, returning `5` both in the definitions and in the interactive. Thanks. I guess the problem was that there was a mixup between the builtin `last-pair` and what I was defining. You can suggest an answer if you would like, and I will confirm it. – kalaracey Apr 01 '13 at 00:26
  • I've added an answer with two new ways to "fix it" at the REPL. Please do try them both out, and tell whether they both worked. – Will Ness Apr 01 '13 at 10:55

2 Answers2

3

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).

soegaard
  • 30,661
  • 4
  • 57
  • 106
  • 1
    I don't think that's what `list` returns, though the recurrence of `cons` may be how the return value is generated. `list` returns a list of (in this case) five elements, plus null. When `(null? (cdr alist))` holds true, `alist` is a list such as `(5)`. Then, `(car list)` should return `5`. – kalaracey Mar 30 '13 at 20:43
  • 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? – kalaracey Mar 30 '13 at 20:45
  • 1
    If alist is (5), it is correct that (car alist) is 5. The value 5 is the last element of the list. The last pair (aka cons-cell) in the list is (cons 5 '()). Since the function is called last-pair and not last-element, I'd expect it to return (cons 5 '()) and that value is printed as (5). – soegaard Mar 30 '13 at 21:40
  • The indentation shouldn't change the value of the program. Try again and if you still get two different result post the exact code here, so we can try it ourselves. – soegaard Mar 30 '13 at 21:41
  • Sorry, I meant that at the end of the recursion, `alist` is just `(5)`, and so `(car alist)` should just return `5`. Also, is there a difference between last-pair and last-element? – kalaracey Mar 30 '13 at 23:23
  • I added an explanation of the difference between the interaction and definition window. – soegaard Mar 31 '13 at 12:04
  • 1
    One more thing: I can recommend using the stepper. See http://stackoverflow.com/questions/10499514/y-combinator-discussion-in-the-little-schemer/10500358#10500358 – soegaard Mar 31 '13 at 12:07
  • thanks for confirming this. perhaps showing a warning there would be good enough. – Will Ness Mar 31 '13 at 19:18
  • the OP now says it still doesn't work at the REPL even with a renamed name. Very strange. Can you confirm this on your installation of Racket? (I don't have one installed at the moment). – Will Ness Mar 31 '13 at 20:59
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.

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)
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Sorry if I wasn't clear, I tried to edit my question to reflect that there was no longer any weird behavior with the REPL. The only issue was where I defined the procedure, and what I named the procedure. Thank you for the detailed answer. – kalaracey Apr 01 '13 at 22:04
  • "there was no longer weird behaviour with the REPL" - after you renamed the function throughout? But I wanted to also find the reason and check the solutions for fixing it without renaming (both of them). – Will Ness Apr 02 '13 at 07:12