Почему это возвращает список '(5), а не номер 5?

Я работаю через 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-схема, кажется, не делает этого. last-pair возвращается 5 не '(5), Что правильно?!?

РЕДАКТИРОВАТЬ 2: Интересно, что в DrRacket (не в MIT-схеме), если вторая строка (cond ((null? (cdr alist)) имеет отступ в два пробела, при вызове процедуры возвращается '(5), Но когда вторая строка не имеет отступа, она возвращает 5, Это глюк? Я полагаю, что все, что интерпретаторы Scheme должны следовать, это круглые скобки, верно?

РЕДАКТИРОВАТЬ 3: я начинаю думать, что это сбой в DrRacket. Когда я помещаю определение процедуры в окно определений (обычно это верхняя панель редактора), независимо от отступа, процедура возвращает 5, Но если я определю это в окне интерфейса, отступ влияет на результат, как описано в Edit 2. (РЕДАКТИРОВАТЬ 4) независимо от отступа, он также вернется '(5),

< отрезанная часть с кодом различий в отступах; проблема сейчас в том, где процедура определена, см. Edit 4 >

РЕДАКТИРОВАТЬ 4: Хорошо, я упростил проблему.

  • В MIT-схеме, (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). Независимо от отступа.

Вот скриншот: Ракетка дает разные результаты для одной и той же функции, определенной в разных окнах

2 ответа

Решение

Лучше не используйте встроенное имя 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 формы, (define f (letrec ((f (lambda(x) ...body...))) f)), А также, что define в Racket REPL не изменяет старую привязку в глобальной среде, но добавляет новую привязку в нее поверх старой, скрывая старую привязку.

Это предлагает еще один способ "исправить это" в REPL - с set!:

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

Поскольку (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),

Вкратце: путаница связана с переопределением встроенной функции в окне взаимодействия. Переопределения обрабатываются, как и ожидалось, в окне определения. Несмотря на путаницу, существуют причины, объясняющие поведение окна взаимодействия (решение этой проблемы, в свою очередь, приведет к путанице в другом месте).

Другие вопросы по тегам