Песочница

Замыкания и общее изменяемое состояние

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

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

LISPEX
(define counter (let ((n 0)) (lambda () (set! n (+ n 1)) n)))
(list (counter) (counter))

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

OUTPUT
(1 2)

Разберите приём

  1. Создайте закрытую ячейку. Внешний let один раз выделяет n при создании counter.
  2. Верните поведение, а не ячейку. После завершения let только lambda имеет доступ к этому связыванию.
  3. Наблюдайте общие изменения. Оба вызова используют одно замыкание, поэтому второй видит set! первого.

Как рассуждать

  • Создайте состояние во внешнем let, верните процедуры чтения/изменения и оставьте ячейку доступной только через них.
  • Затенённые связывания получают другие ячейки; set! обновляет ближайшее разрешённое связывание.
  • Верхнеуровневое переопределение обновляет исходную глобальную ячейку, видимую старым замыканиям.

Проверьте себя

Если дважды вычислить внешний let и создать counter-a и counter-b, изменит ли вызов первого состояние второго?

Ответ

Нет. Каждое вычисление выделяет отдельную лексическую ячейку. Состояние общее лишь у замыканий, захвативших одну ячейку.

Частая ошибка

Общее изменение здесь детерминировано, но всё равно требует узкого владения и явных тестов.

Куда дальше

Точные правила ячеек и затенения находятся в связываниях. Если состояние можно сделать явным входом, руководство по решениям будет проще.

Связывания и область · Решающие правила