При описании различных возможностей Common Lisp’а очень важными понятиями являются области и продолжительности видимости. Эти понятия возникают, когда к некоторому объекту или конструкции необходимо обратиться из некоторой части кода. Область видимости объектов отмечает пространственный или текстовый регион, в котором находящаяся внутри программа может обращаться к этим объектам. Продолжительность видимости обозначает временной интервал, в течение которого программа может обращаться к данным объектам.
Вот простой пример такой программы:
Областью видимости параметра с именем x является тело формы defun. Способа сослаться на этот параметр из какого-либо другого места программы нет. Продолжительностью видимости параметра x (для какого-нибудь вызова copy-cell) является интервал времени, начиная с вызова функции и заканчивая выходом из неё. (В общем случае продолжительность видимости параметра может продлиться и после завершения функции, но в данном простом случае такого не может быть.)
В Common Lisp сущность, на которую можно сослаться из кода, создаётся с помощью специальных языковых конструкций, и область и продолжительность видимости описываются в зависимости от этой конструкции и времени (выполнения конструкции) в которое эта сущность была создана. Для предмета данного описания, термин «сущность» указывает не только на объекты Common Lisp’а такие как символы и cons-ячейки, но и также на связывания переменных (обычных и специальных), ловушки, и метки переходов. Важно отметить различие между сущностью и именем для этой сущности. В определение функции, такой как:
существует только одно имя, x, используемое для ссылки на первый параметр процедуры, когда бы они не была вызвана. Связывание — это, в частности, экземпляр параметра. Значение связанное с именем x зависит не только от области видимости, в которой данная связь возникла (в данном примере в теле функции foo связь возникла в области видимости определения параметров функции), но также, в частности, от механизма связывания. (В данном случае, значение зависит от вызова функции, в течение которого создаётся ссылка). Более сложный пример приводится в конце данной главы.
Вот некоторые виды областей и продолжительностей видимости, которые, в частности, полезны при описании Common Lisp’а:
Например: имена параметров в функции обычно ограничиваются лексической областью видимости.
Например: with-open-file открывает соединение с файлом и создаёт объект потока для отображения этого соединения. Объект потока имеет неограниченную область видимости, но соединение с открытым файлом имеет динамическую продолжительность видимости: когда выполнение в любом, нормальном или аварийном случае, выйдет за рамки конструкции with-open-file, поток будет автоматически закрыт.
Например: связывание «специальной (special)» переменной имеет динамическую продолжительность видимости.
Например: большинство Common Lisp объектов имеют неограниченную продолжительность видимости.
Например: связывание лексически замкнутых параметров функции имеет неограниченную продолжительность видимости. (В отличие от Algol’а, где связывание лексически замкнутых параметров процедуры имеют динамическую продолжительность видимости.) Определение функции
при получении двух параметров, немедленно возвращает функции в качестве результата. Связи параметров для f и g не теряются, потому что возвращённая функция, когда будет вызвана, будет продолжать ссылаться на эти связи. Таким образом
вернёт значение 3.0. (Аналогичная процедура не захочет корректно работать в типичной реализации Algol’а или, даже, в большинстве диалектов Lisp’а.)
В дополнение к вышеназванным терминам, удобно определить динамическую область видимости, которая означает неограниченную область видимости и динамическую продолжительность видимости. Следовательно мы говорим о «специальных (special)» переменных, как об имеющих динамическую область видимости или будучи динамически замкнутых FIXME, потому что они имеют неограниченную область видимости и динамическую продолжительность видимости: к специальным переменным можно сослаться из любой точки программы на протяжении существования их связываний.
Термин «динамическая область видимости» некорректен. Как бы то ни было это и устоялось, и удобно.
Сказанное выше не рассматривает возможность скрытия (shadowing). Далёкие (FIXME) ссылки на сущности осуществляются с использованием имён того или иного типа. Если две сущности имеют одинаковое имя, тогда второе имя может скрыть первое, в таком случае ссылка с помощью этого имени будет осуществлена на вторую сущность и не может быть осуществлена на первую.
В случае лексической области видимости, если две конструкции, которые устанавливают сущности с одинаковыми именами, расположены в тексте одна внутри другой, тогда ссылки внутри внутренней конструкции указывают на сущности внутренней конструкции, то есть внутренние сущности скрывают внешние. Вне внутренней конструкции, но внутри внешней конструкции ссылки указывают на сущности, установленные внешней конструкцией.
Связывание переменной z с помощью конструкции let скрывает связывание одноимённого параметра функции test. Ссылка на переменную z в форме print указывает на let связывание. Ссылка на z в конце функции указывает на параметр с именем z.
В случае динамической продолжительности видимости, если временные интервалы двух сущностей перекрываются, тогда они будут обязательно вложенными один в другого. Это свойство Common Lisp дизайна.
Ссылка по имени на сущность с динамической продолжительностью жизни всегда указывает на сущность с этим именем, что была установлена наипозднейшей и ещё не была упразднена. Например:
Рассмотрим вызов (fun1 7). Результатом будет 10. Во время выполнения throw, существует две ловушки с именем trap: одна установлена в процедуре fun1, и другая — в fun2. Более поздняя в fun2, и тогда, из формы catch, что в fun2, возвращается значение 7. Рассматриваемая из fun3, catch в fun2 скрывает одноимённую в fun1. Если бы fun2 была определена как
тогда бы две ловушки имели разные имена, и в таком случае одна из них из fun1 не была бы скрыта. Результатом бы стало 7.
Как правило, данная книга по простому рассказывает об областях видимости и продолжительности сущности, возможность скрытия оставляется без рассмотрения.
Далее важные правила области и продолжительности видимости в Common Lisp’е:
Правила для лексической области видимости подразумевают, что лямбда-выражения (анонимные функции), появляющиеся в function, будут являться «замыканиями» над этими неспециальными (non-special) переменными, которые видимы для лямбда-выражения. Это значит, что функция предоставленная лямбда-выражением может ссылаться на любую лексически доступную неспециальную (non-special) переменную и получать корректное значение, даже если выполнение уже вышло из конструкции, которая устанавливала связи. Пример compose, рассмотренный в данной главе ранее, предоставлял изображение такого механизма. Правила также подразумевают, что связывания специальных переменных не «замыкаются», как может быть в некоторых других диалектах Lisp’а.
Конструкции, которые используют лексическую область видимости генерируют новое имя для каждой устанавливаемой сущности при каждом исполнении. Таким образом, динамическое скрытие не может произойти (тогда как лексическое может). Это, в частности, важно, когда используется динамическая продолжительность видимости. Например:
Рассмотрим вызов (contorted-example nil nil 2). Он вернёт результат 4. Во время исполнения, contorted-example будет вызвана три раза, чередуясь с двумя блоками:
В время выполнения funcall существует две невыполненные точки выхода block, каждая с именем here. В стеке вызовов выше, эти две точки для наглядности проиндексированы. Форма return-from, выполненная как результат операции funcall, ссылается на внешнюю невыполненную точку выхода (here1), но не на (here2). Это следствие правил лексических областей видимости: форма ссылается на ту точку выхода, что видима по тексту в точке вызова создания функции (здесь отмеченной с помощью синтаксиса #’). (FIXME)
Если в данном примере, изменить форму (funcall f) на (funcall g), тогда значение вызова (contorted-example nil nil 2) будет 9. Значение измениться по сравнению с предыдущим разом, потому что funcall вызовет выполнение (return-from here2 4), и это в свою очередь вызовет выход из внутренней точки выхода (here2). Когда это случиться, значение 4 будет возвращено из середины вызова contorted-example, к нему добавится 5 и результат окажется 9, и это значение вернётся из внешнего блока и вообще из вызова contorted-example. Цель данного примера, показать что выбор точки выхода зависит от лексической области, которая была захвачена лямбда-выражением, когда вызывался код создания этой анонимной функции.
Эта функция contorted-example работает только потому, что функция с именем f вызывается в процессе продолжительности действия точки выхода. Точки выхода из блока ведут себя, как связывания неспециальных (non-special) переменных в имеющимся лексическом окружении, но отличаются тем, что имеют динамическую продолжительность видимости, а не неограниченную. Как только выполнение покинет блок с этой точкой выхода, она перестанет существовать. Например:
Можно предположить, что вызов (illegal-example) вернёт 5: Форма let связывает переменную y со значением выполнения конструкции block; её значение получится равным анонимной функции. Так как y не является числом, она вызывается с параметром 5. return-from тогда должны вернуть данное значение с помощью точки выхода here, тогда осуществляется выход из блока ещё раз и y получает значение 5, которое будучи числом, возвращается в качестве значения для illegal-example.
Рассуждения выше неверны, потому что точки выхода определяемые в Common Lisp’е имеют динамическую продолжительность видимости. Аргументация верна только до вызова return-from. Вызов формы return-from является ошибкой, не потому что она не может сослаться на точку выхода, а потому что она корректно ссылается на точку выхода и эта точка выхода уже была упразднена.