В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 37
Текст из файла (страница 37)
Итак, "пространство имен" модуля ограничено и явно описано.
Упражнение. Сравните с EXTERNAL в Фортране. В чем отличия?
В указателе контекста необходимо перечислять лишь непосредственно
используемые имена библиотечных модулей, т.е. те имена, которые явным
образом присутствуют в тексте модуля. Так, например, если библиотечная
процедура P использует библиотечную процедуру Q, а та, в свою очередь -
библиотечный пакет R, то соответствующие компилируемые модули должны иметь
вид
with R; with Q;
procedure Q is -- with R писать не надо!
... procedure P is
begin begin
... ...
R.P1; -- вызов процедуры, Q; -- вызов Q;
-- описанной в R;
... ...
end Q; end P;
Правила ЯП не запрещают указывать имя "лишнего" модуля, однако, как мы
далее увидим, это не просто бессмысленно, но и опасно.
9.3. Порядок компиляции и перекомпиляции (создания и модификации
программной библиотеки)
Очевидно, что этот порядок не может быть абсолютно произвольным. Нам
уже известно все, чтобы сформулировать требования к нему. Выделим две группы
требований, обусловленные двусторонним и односторонним связыванием
соответственно. Напомним, что в исходном состоянии библиотека содержит лишь
предопределенные библиотечные модули.
Двусторонние связи:
1. Тело следует компилировать после спецификации.
Следствия: После перекомпиляции спецификации необходимо
перекомпилировать тело. Перекомпиляция тела не требует перекомпиляции
спецификации.
2. Вторичный модуль следует компилировать позже соответствующего
родительского модуля.
Следствие. Перекомпиляция родительского модуля влечет перекомпиляцию
всех его вторичных модулей.
Односторонние связи:
Использующий модуль следует компилировать позже используемого (т.е.
модуль можно компилировать только после компиляции (спецификаций, не тел!)
модулей, перечисленных в его указателе контекста.
Следствия. После перекомпиляции библиотечного модуля (спецификации, а
не тела) необходимо перекомпилировать все использующие модули. Перечислять
"лишние" модули в указателе контекста действительно вредно!
Вопрос. А как в Фортране? (компиляция модулей независимая).
[Реализации дано право квалифицированно "разбираться в ситуации" и
выявлять (для оптимизации) те перекомпиляции, которые фактически не
обязательны.]
Вопрос. За счет чего?
9.4. Резюме : логическая и физическая структуры программы
В Аде следует различать физическую и логическую структуры программы.
Эти понятия тесно связаны, но не эквивалентны. Логическая структура - это
абстракция физической структуры, а именно абстракция от конкретного способа
разбиения на (физические) модули.
Во всех случаях, когда важен смысл, а не особенности жизненного цикла
программы (создание, компиляция, хранение, модификация), нас интересует
логическая структура программы. Однако в остальных случаях приходится
учитывать (и использовать) ее физическую структуру.
Физическая структура программы образуется совокупностью (компилируемых)
модулей, отделенных друг от друга, и "подаваемых" компилятору в определенном
порядке. Логическую структуру готовой (завершенной) программы образуют
сегменты. (Иногда их называют "программные модули"). Здесь уже совершенно не
важен порядок компиляции. Более того, при переходе от физической к
логической структуре меняется само понятие компоненты программы. Так, с
точки зрения физической структуры спецификация и тело (библиотечного) пакета
- это разные модули, а в логической структуре - это единый сегмент - пакет,
в котором при необходимости выделяется, например, видимая часть, не
совпадающая в общем случае со спецификацией. (Из-за чего?)
На уровне логической структуры фактически пропадает разница между
первичными и вторичными модулями, а также становится ненужным само понятие
библиотеки.
Каковы же средства создания логической структуры программы над ее
физической структурой? Часть из них мы рассмотрели - это способы
одностороннего и двустороннего связывания: правила соответствия между
спецификацией и телом библиотечного модуля, заглушки и (полные) имена
родительских модулей в заголовках вторичных модулей, указатель контекста.
Другую группу средств связывания образуют правила видимости
идентификаторов и правила идентификации имен. Об этом - в следующем разделе.
10. Именование и видимость (на примере Ады)
10.1. Имя как специфический знак
Идентификация и видимость имен - это два важных аспекта общей проблемы
именования в ЯП, которую мы и рассмотрим на примере Ады, выделяя по
возможности общие принципы и концепции.
Начнем с основных терминов : имя и идентификация имени. Программа на ЯП
представляет собой иерархию знаков. Для некоторых знаков если не сам
денотат, то хотя бы его класс определяется в значительной степени по
структуре знака (оператор присваивания, цикл, объявление процедуры).
Имя как специфический знак характеризуется тем, что по одной его
структуре (т.е. только по внешнему виду знака, без привлечения контекста) в
общем случае нельзя получить никакой информации о денотате.
Например, по одному только знаку "А", "А.В.С.D" или "А(В(С(D)))" в Аде
невозможно сказать не только то, что конкретно он обозначает, но и даже
приблизительно определить класс обозначаемой сущности (процедура,
переменная, тип, пакет и т.п.).
Итак, во-первых, имя бессмысленно без контекста. Во-вторых, оно служит
основным средством связывания различных контекстов (фрагментов программы) в
единое целое. В-третьих, денотат имени можно извлечь только из анализа
контекста. Процесс-правила-алгоритм-результат такого анализа называют
идентификацией имени.
10.2. Имя и идентификатор
В Аде любое имя содержит хотя бы один идентификатор. Идентификатор -
это атомарное имя (т.е. никакое имя не может быть частью идентификатора).
Идентификатор в Аде строится, как и в других ЯП, из букв и цифр, которые (в
отличие от многих других ЯП) можно разделять единичными подчеркиваниями.
Только идентификаторы можно непосредственно объявлять в программе.
Денотаты других имен вычисляются через денотаты их компонент-
идентификаторов.
[В Аде особую роль играют предопределенные знаки операций ("+", "-" и
др.). Их можно использовать только в строго определенных синтаксических
позициях, а именно в позициях операций в выражениях. Соответственно и
переопределять такие знаки разрешается только с учетом указанного
требования. Все это делается, чтобы обеспечить стабильность синтаксиса
(привычка программиста - гарантия надежности, да и анализ выражений проще).
Для знаков операций проблема идентификации стоит так же, как и для обычных
идентификаторов. Мы не будем их особо отличать.]
10.3. Проблема видимости
Вхождение идентификатора в Ада-программу может быть либо определяющим,
либо использующим. Во всяком ЯП с достаточно сложной структурой программы
существует проблема установления соответствия между определяющими и
использующими вхождениями. Следуя адовской терминологии, будем называть ее
проблемой видимости идентификаторов. Полная проблема идентификации имен
включает проблему видимости идентификаторов, но не сводится к ней.
Вопрос. В чем различие?
Подсказка. Имена бывают не только идентификаторами. К тому же мало
найти определяющее вхождение, нужно еще и вычислить денотат.
Заметим, что следует различать статическую и динамическую
идентификацию. Так, если объявлено :
A: array (1..10) of INTEGER ;
I: INTEGER ;
то со статической точки зрения имя A(I) обозначает элемент массива A, но
динамическая идентификация при I=3 даст A(3) (т.е. 3-й элемент), а при I=11
- исключение нарушение_диапазона.
10.4. Аспекты именования
Именование - средство построения логической структуры программы над ее
физической структурой в том смысле, что после того, как в компилируемом
модуле идентифицированы все имена, он становится составной частью теперь уже
логической структуры программы, поскольку оказывается связанным со всеми
используемыми в нем понятиями и элементами программного комплекса как
единого целого.
Выделим следующие относительно независимые аспекты именования:
разновидности объявлений; строение имен; строение "пространства имен";
правила видимости идентифмкаторов; схема идентификации имен.
Всюду ниже в этом разделе будем игнорировать физическую структуру
программы (ее разбиение на модули). Учитывать будем лишь ее логическую
структуру (разбиение на сегменты). Таким образом, исключаем из рассмотрения
имена модулей и их связывание.
Применим к проблеме именования уже не раз нами использованный принцип
технологичности: от технологической потребности через определяющие
требования к выразительным средствам (языковым конструктам).
10.5. Основная потребность и определяющие требования
Основная "внешняя" технологическая потребность очевидна - точно
называть необходимые компоненты программы. Однако поскольку эти компоненты
разнородны и обслуживают весьма разнообразные потребности, то существует
сложное и многогранное "внутреннее" определяющее требование : именование
должно быть хорошо согласовано со всеми средствами ЯП и должно отвечать
общим требованиям к нему (надежность, читаемость, эффективность и т.п.).
Другими словами, концепция именования и основные конструкты ЯП (а также
заложенные в них концепции) взаимозависимы.
[Сложные и многообразные конструкты ведут к сложному именованию, и
наоборот, относительно простые способы именования требуют относительной
простоты конструктов (сравните именование в Аде с именованием в Бейсике или
в Фортране). Искусство автора ЯП проявляется в умении найти разумный
компромисс между собственной сложностью ЯП и сложностью его использования
для сложных задач (Фортран или Бейсик относительно просты, но сложные задачи
на них программировать труднее, чем на Аде)].
При создании Ады приоритет имела задача включения в него богатого
набора средств (конструктов), позволяющих адекватно реализовывать большой
набор технологических потребностей, причем для многих технологических
потребностей уже в самом ЯП заготавливалось готовое специальное средство
(например, потребность в родовой настройке может быть удовлетворена за счет
специализированного макропроцессора, а не встраиваться в ЯП
непосредственно).
В результате именование получилось довольно сложным. Это признают и
авторы языка (Ледгар совместно с Зингером даже отстаивал идею стандартного
подмножества Ады, чего никак не хотел допустить заказчик - МО США [32]).
Значительная часть критических замечаний в адрес Ады также касается
идентификации имен.
10.6. Конструкты и требования, связанные с именованием
Выделим общие требования к языку и те конструкты Ады, которые оказались
существенно связанными с именованием.
Глубокая структуризация языковых объектов:
1. Составные типы данных. Чтобы понять, какие в связи с этим возникают
требования к именам, достаточно представить себе многоуровневую структуру из
массивов и записей. Каждую ее компоненту надо уметь называть, вот и
появились имена вида
A(I,J,K).B(L,M).C.D(E,F).K.all.E
Упражнение. Постройте примеры соответствующих структур.
2. Пакет как логически связанная совокупность ресурсов. Мало объявить и
реализовать ресурс, надо дать возможность им пользоваться. Как минимум, надо
дать возможность его называть. Чтобы "развязать" именование ресурсов в
непересекающихся пакетах (зачем это нужно?), приходится вводить составные
(полные) имена. Другими словами, сначала называется имя пакета, а затем
(через точку) имя ресурса в пакете.
3. Задача как автономное описание класса асинхронных процессов.
Проблема именования входов задач решается аналогично пакетам : вызывая вход,
надо назвать сначала задачу, где он объявлен, а уж затем сам вход.
Раздельная компиляция. Раздельно компилируемые модули должны иметь
доступ к именам друг друга. Иначе создавать программу невозможно.