Когда это нужно
Связывание даёт результату имя, создаёт локальную область или намеренно сохраняет состояние между вызовами. Обычно достаточно define и let. Используйте set!, только если совместное изменение входит в модель.
Проверьте запуском
(define x 1)
(define get (lambda () x))
(set! x 2)
(get)Наблюдаемый результат
2Разбор примера
get захватывает глобальную ячейку x, а не замороженную копию числа 1. set! записывает 2 в ту же ячейку, поэтому следующий вызов видит 2. Новая внутренняя x лишь затенила бы внешнюю.
let* создаёт имена по очереди сверху вниз, поэтому следующее начальное выражение видит только что созданное имя.
(let* ((x 6)
(y (+ x 1)))
(* x y))Наблюдаемый результат
42letrec выделяет все ячейки до вычисления начальных выражений, и именно поэтому имя может вызвать само себя.
(letrec ((f (lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
(f 5))Наблюдаемый результат
120Как рассуждать
- Поиск выбирает ближайшую лексическую ячейку, а затенение не изменяет внешнее связывание.
letrecвыделяет ячейки до инициализаторов и выдаёт E321 при чтении неинициализированной ячейки.- Повторный верхнеуровневый
defineобновляет существующую глобальную ячейку, сохраняя ссылки старых замыканий.
Быстрый выбор
| Форма | Назначение | Где вычисляются инициализаторы |
|---|---|---|
define | верхнеуровневое имя или именованная процедура | текущая верхняя область |
let | независимые локальные значения | внешняя область |
let* | локальные значения с зависимостями по порядку | все предыдущие связывания |
letrec | взаимно рекурсивные локальные процедуры | все выделенные ячейки, но раннее чтение ошибочно |
set! | изменение существующей ячейки | новое связывание не создаётся |
Частая ошибка
set! для несвязанного имени даёт E303 и не создаёт связывание.
Текущие границы
- Внутренний маркер неинициализированной ячейки нельзя представить гостевым значением.
Куда дальше
В главе о процедурах показан захват ячеек, а руководство по замыканиям превращает это правило в небольшой пример с состоянием.