Проблема с круговым определением в схеме

StackOverflow https://stackoverflow.com/questions/2828996

  •  26-09-2019
  •  | 
  •  

Вопрос

В настоящее время я работаю над 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top