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