Песочница

Имена, область видимости и изменение значений

Дайте значениям имена с помощью define и let, затем измените через set! значение, которое читает процедура.

Когда это нужно

Дайте значениям имена с помощью define и let. Используйте set!, если при следующем вызове процедура должна прочитать изменённое значение. Связь имени со значением называется связыванием, и каждое связывание хранит значение в одной ячейке.

Проверьте запуском

Сохраните всю программу ниже в bindings.lspx. Сначала установите Native CLI или запускайте каждый пример отдельно в песочнице.

LISPEX
(define x 1)
(define get (lambda () x))
(set! x 2)
(get)
SH
lispex run bindings.lspx

Вывод

ВЫВОД
2

Команда завершается с кодом 0 и не создаёт файлов.

Разбор примера

get читает x при вызове. К этому моменту set! уже меняет x с 1 на 2, поэтому (get) возвращает 2.

Связывание соединяет имя с местом хранения значения. Это место называется ячейкой. set! меняет значение в существующей ячейке.

Область видимости — часть программы, в которой можно использовать имя. В Лиспексе область видимости лексическая: структура исходного кода определяет, к какому связыванию относится имя.

Используйте локальное имя

Внутреннее имя может скрывать внешнее. Запустите этот пример отдельно от первого.

LISPEX
(define x 1)
(define get (lambda () x))
(list (let ((x 10)) (set! x 11) x) (get))

Результат

ВЫВОД
(11 1)

У внутренней x своя ячейка. После изменения её значения на 11 внешняя x остаётся равной 1. Это называется затенением. get по-прежнему читает внешнюю x, доступную при создании процедуры.

Используйте предыдущее локальное значение

LISPEX
(let* ((x 6)
       (y (+ x 1)))
  (* x y))

Наблюдаемый результат

ВЫВОД
42

let* создаёт имена сверху вниз. Поэтому выражение (+ x 1) может использовать x, уже равную 6. Выражение, задающее начальное значение связывания, называется инициализатором.

Дайте локальной процедуре вызвать себя

LISPEX
(letrec ((f (lambda (n)
              (if (= n 0)
                  1
                  (* n (f (- n 1)))))))
  (f 5))

Наблюдаемый результат

ВЫВОД
120

f вычисляет факториал числа 5. letrec создаёт все ячейки до вычисления инициализаторов. Поэтому процедура может ссылаться на f в собственном теле. Несколько процедур в одном letrec могут также вызывать друг друга.

Чтение ячейки до присвоения значения вызывает E321. Например, (letrec ((x x)) x) читает x слишком рано. До инициализации Лиспекс хранит в ячейке внутреннюю метку. Программа на Лиспексе не может создать эту метку как значение.

Переопределите имя верхнего уровня

LISPEX
(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 и не создаёт связывание.

Куда дальше

В главе о процедурах описано, как определять и вызывать свои процедуры. Руководство по замыканиям показывает, когда два счётчика разделяют значение, а когда каждый хранит своё.

Процедуры · Руководство по замыканиям