Когда это нужно
Дайте значениям имена с помощью define и let. Используйте set!, если при следующем вызове процедура должна прочитать изменённое значение.
Связь имени со значением называется связыванием, и каждое связывание хранит значение в одной ячейке.
Проверьте запуском
Сохраните всю программу ниже в bindings.lspx. Сначала установите Native CLI или запускайте каждый пример отдельно в песочнице.
(define x 1)
(define get (lambda () x))
(set! x 2)
(get)lispex run bindings.lspxВывод
2Команда завершается с кодом 0 и не создаёт файлов.
Разбор примера
get читает x при вызове. К этому моменту set! уже меняет x с 1 на 2, поэтому (get) возвращает 2.
Связывание соединяет имя с местом хранения значения. Это место называется ячейкой. set! меняет значение в существующей ячейке.
Область видимости — часть программы, в которой можно использовать имя. В Лиспексе область видимости лексическая: структура исходного кода определяет, к какому связыванию относится имя.
Используйте локальное имя
Внутреннее имя может скрывать внешнее. Запустите этот пример отдельно от первого.
(define x 1)
(define get (lambda () x))
(list (let ((x 10)) (set! x 11) x) (get))Результат
(11 1)У внутренней x своя ячейка. После изменения её значения на 11 внешняя x остаётся равной 1. Это называется затенением. get по-прежнему читает внешнюю x, доступную при создании процедуры.
Используйте предыдущее локальное значение
(let* ((x 6)
(y (+ x 1)))
(* x y))Наблюдаемый результат
42let* создаёт имена сверху вниз. Поэтому выражение (+ x 1) может использовать x, уже равную 6. Выражение, задающее начальное значение связывания, называется инициализатором.
Дайте локальной процедуре вызвать себя
(letrec ((f (lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
(f 5))Наблюдаемый результат
120f вычисляет факториал числа 5. letrec создаёт все ячейки до вычисления инициализаторов. Поэтому процедура может ссылаться на f в собственном теле. Несколько процедур в одном letrec могут также вызывать друг друга.
Чтение ячейки до присвоения значения вызывает E321. Например, (letrec ((x x)) x) читает x слишком рано. До инициализации Лиспекс хранит в ячейке внутреннюю метку. Программа на Лиспексе не может создать эту метку как значение.
Переопределите имя верхнего уровня
(define x 1)
(define get (lambda () x))
(define x 3)
(get)Результат
3Определение верхнего уровня находится вне процедуры или формы локального связывания. Повторный define здесь обновляет существующую ячейку. Поэтому ранее созданная get читает новое значение.
Быстрый выбор
| Форма | Назначение | Где вычисляются инициализаторы |
|---|---|---|
define | имя верхнего уровня или именованная процедура | текущие связывания верхнего уровня |
let | независимые локальные значения | внешние связывания |
let* | локальные значения, зависящие от предыдущих | внешние и предыдущие связывания этого let* |
letrec | локальные процедуры, вызывающие себя или друг друга | все связывания этого letrec; чтение неинициализированной ячейки вызывает E321 |
set! | изменение существующей ячейки | связывания, видимые в месте записи set! |
Частая ошибка
set! для несвязанного имени даёт E303 и не создаёт связывание.
Куда дальше
В главе о процедурах описано, как определять и вызывать свои процедуры. Руководство по замыканиям показывает, когда два счётчика разделяют значение, а когда каждый хранит своё.