Меня смущает следующий код:
(define (even-or-odd-letrec x)
((lambda (internal-even? internal-odd?)
(set! internal-even? (lambda (n)
(if (= n 0) 'even
(internal-odd? (- n 1)))))
(set! internal-odd? (lambda (n)
(if (= n 0) 'odd
(internal-even? (- n 1)))))
(internal-even? x))
#f #f))
Насколько я читал, окружение выглядит следующим образом:
even-or-odd-letrec
, internal-even?
и internal-odd?
изначально привязаны к #f
. Их родительской средой является глобальная среда.set!
затем меняет эти два значения на очевидные lambda
s, но не меняет среды.internal-even?
будут искать internal-odd?
в глобальном окружении и ничего не найдут.Так как же работает этот код?
Отвечая на заголовок вашего вопроса: нет, установка не создает новых кадров.
Параметр задает значение привязки в текущем фрейме среды. Какими бы ни были старые и новые ценности. Функция или нет, не имеет значения.
Опять же, set!
не меняет значения. Он изменяет значения привязки. Привязка — это сочетание имени и его значения. Set!
ting изменяет связанное значение этого имени.
Рассматриваемое как именованный указатель, после повторного set!
имя указывает на новое значение — то значение, которое было передано set!
в качестве второго аргумента.
Итак, вопреки тому, что вы говорите, set!
делает сдача текущий фрейм среды - он изменяет свою привязку для заданного имени, которое установлено.
+---------------+ +---------------+
| n1 ---> v1 | | n1 ---> x |
| n2 ---> v2 | (set! n1 x) | n2 ---> v2 |
|---------------| -------------> |---------------|
| ..code... | | ..code... |
|_______________| |_______________|
«Любой звонок в internal-even?
будет» ..... стоп! это не правильный взгляд на это.
Любая ссылка на internal-even?
внутри фрейма, созданного lambda
, будет разрешена путем поиска значения под именем internal-even?
в том же фрейме, и все будет работать нормально.
Когда вы (set! internal-even? (lambda (...) ... internal-odd? ...))
, это правда, что ценностьinternal-odd?
все еще не то, чем должно быть, но это нормально, потому что его значение еще не просматривается, потому что internal-even?
еще не запущен. Когда вычисляется лямбда-выражение (здесь, как часть вызова set!
), его значением является функция, которая говорит: когда придет время, когда эта функция запустится, потом ищет в ней значение любого имени по мере необходимости.
При применении к двум значениям аргумента (lambda (internal-even? internal-odd?) ...)
создает новую среду с привязками для internal-even?
и internal-odd?
. Все в этой среде, которое ссылается на internal-even?
или internal-odd?
, будет ссылаться на текущее значение этих привязок во время выполнения кода. set!
мутирует эту среду, изменив значения привязок, так что текущие значения этих привязок теперь представляют собой две процедуры, которые сами ссылаются на текущие значения этих привязок.
На мой взгляд, самый простой способ понять это — реализовать небольшой интерпретатор. Это очень легко сделать: вы используете списки для сред, оценщику нужно довольно много обрабатывать if
, lambda
и set!
как особые случаи. Каждый должен написать один из них.
@ J.Mini в конце концов, вы объясняете свое замешательство!.... :) Я отредактировал свой ответ с объяснением.
@J.Mini: как сказал Уилл Несс, при оценке такой формы, как (lambda (...) ...)
, нет просматривает значения переменных в теле функции: он может проверять, что эти переменные существовать (или будут существовать, в случае переменных, связанных функцией себя) и жалуются, если они этого не делают (например, Racket делает это), но они не просматриваются до тех пор, пока функция не будет вызвана, и значения, которые извлекаются, являются значениями, которые существуют в этот момент.
Установка переменной не привязывает новое местоположение. Обратите внимание, однако, что привязка новой переменной не означает создание/увеличение кадра. Можно (как и в C) хранить переменные в регистрах, а не во фреймах, или вообще не хранить их, если вы можете доказать что-то о поведении переменной.
Я думаю, что меня смущает то, что
set!
должен оценивать свой второй аргумент, и оценка этого второго аргумента вызовет бессмыслицу. Например, повторное связывание (и, следовательно, оценка)internal-even?
, когдаinternal-odd?
все еще установлено на#f
, не даст вам того, что вы хотите.