Проблема с круговым определением в схеме
Вопрос
В настоящее время я работаю над SICP, используя Guile в качестве основного языка для упражнений.Я обнаружил странное поведение при выполнении упражнений из главы 3.5.Я воспроизвел это поведение, используя Guile 1.4, Guile 1.8.6 и Guile 1.8.7 на различных платформах, и уверен, что это не характерно для моей установки.
Этот код работает нормально (и вычисляет e):
(define y (integral (delay dy) 1 0.001))
(define dy (stream-map (lambda (x) x) y))
(stream-ref y 1000)
Следующий код должен дать идентичный результат:
(define (solve f y0 dt)
(define y (integral (delay dy) y0 dt))
(define dy (stream-map f y))
y)
(stream-ref (solve (lambda (x) x) 1 0.001) 1000)
Но это дает сообщение об ошибке:
standard input:7:14: While evaluating arguments to stream-map in expression (stream-map f y):
standard input:7:14: Unbound variable:
y ABORT: (unbound-variable)
Таким образом, при внедрении в определение процедуры (define y...) не работает, тогда как вне процедуры в глобальной среде REPL он работает нормально.
Что я здесь делаю не так?При необходимости я также могу опубликовать вспомогательный код (т. е. определения интеграла, карты потока и т. д.).За исключением системно-зависимого кода для cons-stream, все они описаны в книге.Моя собственная реализация cons-stream для Guile выглядит следующим образом:
(define-macro (cons-stream a b)
`(cons ,a (delay ,b)))
Решение
Ключевое различие между тем, что происходит, когда вы оцениваете определения один за другим в REPL и когда вы размещаете их внутри solve
это то, что в первом случае они оцениваются последовательно, поэтому y
выражение (stream-map <some-function> y)
Относится к уже в области применения, тогда как с внутренними определениями или letrec
, это еще не доступно.
Схема MIT Smipple, которую я использовал при прохождении через SICP, не имел такой проблемы и все еще лечит letrec
И внутренний определяет по-разному:
;; this is an error
(letrec ((xs '(1 2 3)) (ys (map (lambda (x) (+ x 1)) xs))) ys)
;; this is still an error (and is treated as such by Guile),
;; yet evaluates to (2 3 4) in MIT Scheme
(let () (define xs '(1 2 3)) (define ys (map (lambda (x) (+ x 1)) xs)) ys)
Я не уверен в оригинальном «пересмотренном отчете о схеме алгоритмии языка» или R2RS, но, по крайней мере, от R3R на внутренних определениях должен быть эквивалентен letrec
. Отказ Видимо, эта особенность окружающей среды MIT повлияла на книгу ... или, возможно, это наоборот.
Другие советы
Вы не можете иметь внутренние определяющие, которые зависят друг от друга; Язык SPEC явно заявляет об этом (R5RS 5.2.2):
... должно быть возможно оценить каждый выражение каждого внутреннего определения в тело не назначая или ссылаясь на значение любого переменная быть определенным.
Вы можете думать об этом, как будто переводчик собирает все определяющие и оценивать их до тела в случайном порядке. Поскольку заказ случайно, не может быть никаких взаимозависимостей, если вы ожидаете его работать.
В решающем определении (# 71) есть даже сноска (# 71), что говорит, что она не собирается работать на всех схемах.
Вы должны написать код так, чтобы одно определение очень четко в объеме другого, вроде с вложенным дающим:
(Определите (решить f y0 dt) (пусть ((y (интеграл (задержка dy) y0 dt))) (пусть (dy (stepe-map fy))) y)))
Следуя идее в комментариях (ссылаясь на цитату из R5RS 4.2.2) я сейчас обернул определения "y
" и "dy
" в (lambda () ...)
с:
(define (solve f y0 dt)
(define (y) (integral (delay (dy)) y0 dt))
(define (dy) (stream-map f (y)))
(y))
Это гарантирует, что <init>
часть каждого определения может быть вычислена без обращения к переменным, определенным в цикле, поскольку определения представляют собой процедуры, а не выражения с другими переменными, как в исходном случае.
Теперь код, конечно, работает намного медленнее (поскольку функции будут рекурсивно переноситься) и размер стека необходимо увеличить, но следующее работает и дает правильный результат:
(debug-set! stack 2000000)
(stream-ref (solve (lambda (x) x) 1 0.001) 1000)
С аналогичной модификацией пример кода Михала работает, как только определяются процедуры, а не переменные:
(let ()
(define (xs) '(1 2 3))
(define (ys) (map (lambda (x) (+ x 1)) (xs)))
(ys))
работает на Гуиле 1.8.6.