Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки программирования. Концепции и принципы
.pdf
Библиотека
9.1. Структура библиотеки ............ 202
9.2. Компилируемый
(трансляционный) модуль ............. 202
9.3. Порядок компиляции
и перекомпиляции (создания
и модификации программной
библиотеки)................................... 203
9.4. Резюме: логическая
и физическая структуры
программы .................................... 204
Глава 9

202
Современное состояние языков программирования
9.1. Структура библиотеки
До сих пор мы избегали подробного описания языковых свойств, ограничиваясь
сведениями, достаточными для демонстрации рассматриваемых концепций и
принципов. Однако в будущем мы намерены существенно затронуть авторскую
позицию, для которой, конечно, важны все тонкости ЯП (иначе они бы в нем не
появились). Более того, мы намерены изложить принципы, в определенном смыс
ле управляющие сложностью создаваемого ЯП. Для их понимания необходимо,
чтобы читатель был в состоянии в деталях сопоставить решения, принятые авто
рами различных ЯП.
Поэтому в ближайших разделах, завершая знакомство с основными языковыми
абстракциями, мы подробно остановимся на избранных аспектах Ады, а именно на
раздельной компиляции, управлении видимостью идентификаторов и обмене
с внешней средой. Основная цель упоминания подробностей – продемонстриро
вать сложность языка и возникающие в этой связи проблемы. Заинтересованного
читателя отсылаем к руководствам по Аде [16, 17, 18].
Ранее мы рассмотрели виды связывания раздельно транслируемых модулей
в Аде. Посмотрим на ту же самую проблему немного с другой стороны – обсудим
устройство программной (трансляционной) библиотеки. Ада – первый ЯП, в ко
тором особенности использования библиотеки тщательно проработаны и зафик
сированы в определении ЯП. В этом отношении полезно сравнить Аду, например,
с Фортраном.
9.2. Компилируемый
(трансляционный) модуль
Компилятор получает «на вход» компилируемый модуль, который состоит из
(возможно, пустой) спецификации контекста и собственно текста модуля. Специ
фикация контекста содержит указатели контекста (with) и сокращений (use).
Займемся первым.
Уже было сказано, что with – средство явного указания односторонней связи:
в использующем модуле перечисляют имена необходимых ему библиотечных мо
дулей.
Таким образом, любое имя, используемое в данном модуле, должно быть либо
объявлено в самом этом модуле или в связанных с ним (при помощи двусторон
ней связи!) библиотечных или родительских модулях; либо объявлено в пакете
STANDARD; либо предопределено; либо явно перечислено в указателе контекста
(with).
Итак, «пространство имен» модуля ограничено и явно описано.
Упражнение. Сравните с EXTERNAL в Фортране. В чем отличия?
В указателе контекста необходимо перечислять лишь непосредственно ис
пользуемые имена библиотечных модулей, то есть те имена, которые явным обра

Библиотека
зом присутствуют в тексте модуля. Например, если библиотечная процедура Р
использует библиотечную процедуру Q, а та – в свою очередь, библиотечный пакет
R, то соответствующие компилируемые модули должны иметь следующий вид:
with R; with Q;
procedure Q is -- with R писать не надо!
... procedure P is
begin begin
... ...
R.P1; Q; -- вызов Q;
-- вызов процедуры, ...
-- описанной в R; end P;
...
end Q;
Правила ЯП не запрещают указывать имя «лишнего» модуля, однако, как мы
далее увидим, это не просто бессмысленно, но и опасно.
203
9.3. Порядок компиляции
и перекомпиляции
(создания и модификации
программной библиотеки)
Очевидно, что этот порядок не может быть абсолютно произвольным. Нам уже
известно все, чтобы сформулировать требования к нему. Выделим две группы
требований, обусловленные двусторонним и односторонним связываниями соот
ветственно. Напомним, что в исходном состоянии библиотека содержит лишь
предопределенные библиотечные модули.
Двусторонние связи:
1. Тело следует компилировать после спецификации.
Следствия. После перекомпиляции спецификации необходимо пе
рекомпилировать тело. Перекомпиляция тела не требует перекомпиляции
спецификации.
2. Вторичный модуль следует компилировать позже соответствующего роди
тельского модуля.
Следствие. Перекомпиляция родительского модуля влечет перекомпиля
цию всех его вторичных модулей.
Односторонние связи:
Использующий модуль следует компилировать позже используемого (то есть
модуль можно компилировать только после компиляции (спецификаций, не тел!)
модулей, перечисленных в его указателе контекста.
Следствия. После перекомпиляции библиотечного модуля (спецификации,
а не тела) необходимо перекомпилировать все использующие модули. Перечис
лять «лишние» модули в указателе контекста действительно вредно!

204
Вопрос. А как в Фортране (где компиляция модулей независимая)?
Реализации дано право квалифицированно «разбираться в ситуации» и выявлять
(для оптимизации) те перекомпиляции, которые фактически не обязательны.
Вопрос. За счет чего?
Современное состояние языков программирования
9.4. Резюме: логическая
и физическая структуры программы
В Аде следует различать физическую и логическую структуры программы. Эти
понятия тесно связаны, но не эквивалентны. Логическая структура – это абстрак
ция физической структуры, а именно абстракция от конкретного способа разбие
ния на (физические) модули.
Во всех случаях, когда важен смысл, а не особенности жизненного цикла про
граммы (создание, компиляция, хранение, модификация), нас интересует логи
ческая структура программы. Однако в остальных случаях приходится учитывать
(и использовать) ее физическую структуру.
Физическая структура программы образуется совокупностью (компили
руемых) модулей, отделенных друг от друга и «подаваемых» компилятору в опре
деленном порядке. Логическую структуру готовой (завершенной) программы
образуют сегменты. (Иногда их называют «программные модули».) Здесь уже со
вершенно не важен порядок компиляции. Более того, при переходе от физической
к логической структуре меняется само понятие компоненты программы. Так,
с точки зрения физической структуры, спецификация и тело (библиотечного) па
кета – это разные модули, а в логической структуре это единый сегмент – пакет,
в котором при необходимости выделяется, например, видимая часть, не совпа
дающая в общем случае со спецификацией. (Изза чего?)
На уровне логической структуры фактически пропадает разница между пер
вичными и вторичными модулями, а также становится ненужным само понятие
библиотеки.
Каковы же средства создания логической структуры программы над ее физи
ческой структурой? Часть из них мы рассмотрели – это способы одностороннего
и двустороннего связывания: правила соответствия между спецификацией и те
лом библиотечного модуля, заглушки и (полные) имена родительских модулей
в заголовках вторичных модулей, указатель контекста.
Другую группу средств связывания образуют правила видимости идентифи
каторов и правила идентификации имен. Об этом – в следующем разделе.

Глава 10
Именование и видимость
(на примере Ады)
10.1. Имя как специфический
знак ............................................... 206
10.2. Имя и идентификатор ........... 206
10.3. Проблема видимости ............ 206
10.4. Аспекты именования ............. 207
10.5. Основная потребность
и определяющие требования ........ 207
10.6. Конструкты и требования,
связанные с именованием............. 208
10.7. Схема идентификации .......... 210
10.8. Недостатки именования
в Аде .............................................. 216

206
Современное состояние языков программирования
10.1. Имя как специфический знак
Идентификация и видимость имен – это два важных аспекта общей проблемы
именования в ЯП, которую мы и рассмотрим на примере Ады, выделяя по воз
можности общие принципы и концепции.
Начнем с основных терминов: имя и идентификация имени. Программа на ЯП
представляет собой иерархию знаков. Для некоторых знаков если не сам денотат,
то хотя бы его класс определяется в значительной степени по структуре знака
(оператор присваивания, цикл, объявление процедуры).
Имя как специфический знак характеризуется тем, что по одной его структуре
(то есть только по внешнему виду знака, без привлечения контекста) в общем слу
чае нельзя получить никакой информации о денотате.
Например, по одному только знаку "A", "A.B.C.D" или "A(B(C(D)))" в Аде не
возможно сказать не только то, что конкретно он обозначает, но даже приблизи
тельно определить класс обозначаемой сущности (процедура, переменная, тип,
пакет и т. п.).
Итак, вопервых, имя бессмысленно без контекста. Вовторых, оно служит ос
новным средством связывания различных контекстов (фрагментов программы) в
единое целое. Втретьих, денотат имени можно извлечь только из анализа контек
ста. Процесс (правила, алгоритм, результат) такого анализа называют идентифи
кацией имени.
10.2. Имя и идентификатор
В Аде любое имя содержит хотя бы один идентификатор. Идентификатор – это
атомарное имя (то есть никакое имя не может быть частью идентификатора).
Идентификатор в Аде строится, как и в других ЯП, из букв и цифр, которые (в отли
чие от многих других ЯП) можно разделять единичными подчеркиваниями.
Только идентификаторы можно непосредственно объявлять в программе. Дено
таты других имен вычисляются через денотаты их компонентидентификаторов.
В Аде особую роль играют предопределенные знаки операций (+ , – и др.). Их можно
использовать только в строго определенных синтаксических позициях, а именно
в позициях операций в выражениях. Соответственно, и переопределять такие знаки
разрешается только с учетом указанного требования. Все это делается, чтобы обеспе
чить стабильность синтаксиса (привычка программиста – гарантия надежности, да и
анализ выражения проще). Для знаков операций проблема идентификации стоит
так же, как и для обычных идентификаторов. Мы не будем их особо отличать.
10.3. Проблема видимости
Вхождение идентификатора в Адапрограмму может быть либо определяющим,
либо использующим. Во всяком ЯП с достаточно сложной структурой программы
существует проблема установления соответствия между определяющими и ис

Именование и видимость (на примере Ады)
пользующими вхождениями. Следуя адовской терминологии, будем называть ее
проблемой видимости идентификаторов. Полная проблема идентификации имен
включает проблему видимости идентификаторов, но не сводится к ней.
Вопрос. В чем различие?
Подсказка. Имена бывают не только идентификаторами. К тому же мало найти
определяющее вхождение, нужно еще вычислить денотат.
Заметим, что следует различать статическую и динамическую идентифика
ции. Так, если объявлено
A: array(1..10) of INTEGER;
I: INTEGER;
то со статической точки зрения имя А(I) обозначает элемент массива А, но дина
мическая идентификация при I=3 даст А(3) (то есть 3й элемент), а при I=11 –
исключение нарушение_диапазона.
207
10.4. Аспекты именования
Именование – средство построения логической структуры программы над ее фи
зической структурой в том смысле, что после того, как в компилируемом модуле
идентифицированы все имена, он становится составной частью теперь уже логи
ческой структуры программы, поскольку оказывается связанным со всеми ис
пользуемыми в нем понятиями и элементами программного комплекса как едино
го целого.
Выделим следующие относительно независимые аспекты именования: разно
видности объявлений; строение имен; строение «пространства имен»; правила
видимости идентификаторов; схема идентификации имен. Всюду ниже в этом
разделе будем игнорировать физическую структуру программы (ее разбиение на
модули). Учитывать будем лишь ее логическую структуру (разбиение на сегмен
ты). Таким образом, исключаем из рассмотрения имена модулей и их связывание.
Применим к проблеме именования уже не раз нами использованный принцип
технологичности: от технологической потребности через определяющие требо
вания к выразительным средствам (языковым конструктам).
10.5. Основная потребность
и определяющие требования
Основная «внешняя» технологическая потребность очевидна – точно называть
необходимые компоненты программы. Однако поскольку эти компоненты разно
родны и обслуживают весьма разнообразные потребности, то существует сложное
и многогранное «внутреннее» определяющее требование: именование должно
быть хорошо согласовано со всеми средствами ЯП и должно отвечать общим тре
бованиям к нему (надежность, читаемость, эффективность и т. п.).

208
Другими словами, концепция именования и основные конструкты ЯП (а так
же заложенные в них концепции) взаимозависимы.
Сложные и многообразные конструкты ведут к сложному именованию, и наоборот,
относительно простые способы именования требуют относительной простоты кон
структов (сравните именование в Аде с именованием в Бейсике или Фортране).
Искусство автора ЯП проявляется в умении найти разумный компромисс между
собственной сложностью ЯП и сложностью его использования для сложных задач
(Фортран или Бейсик относительно просты, но сложные задачи на них программи
ровать труднее, чем на Аде).
При создании Ады приоритет имела задача включения в него богатого набора
средств (конструктов), позволяющих адекватно реализовывать большой набор
технологических потребностей, причем для многих технологических потребнос
тей уже в самом ЯП заготавливалось специальное средство (например, потреб
ность в родовой настройке может быть удовлетворена специализированным
макропроцессором, а не встраиваться в ЯП непосредственно).
В результате именование получилось довольно сложным. Это признают и ав
торы языка (Ледгар совместно с Зингером даже отстаивали идею стандартного
подмножества Ады, чего никак не хотел допустить заказчик – МО США [32]).
Значительная часть критических замечаний в адрес Ады также касается иденти
фикации имен.
Современное состояние языков программирования
10.6. Конструкты и требования,
связанные с именованием
Перечислим общие требования к языку и те конструкты Ады, которые оказались
существенно связанными с именованием.
Требования. Глубокая структуризация языковых объектов, раздельная ком
пиляция, относительная независимость именования внутри сегментов, необ
ходимость переименования и сокращения длинных имен.
Кроме того, критичность проблемы полиморфизма потребовала заменить
классический (бытовавший в ЯП еще со времен Алгола60) запрет объявлять оди
наковые идентификаторы в одном блоке более гибким ограничением, позволяю
щим объявлять одноименные операции, процедуры и функции с различающими
ся профилями.
Принцип обязательности объявлений для всех имен (кроме предопределен
ных) в сочетании с необходимостью производных типов привел к так называе
мым неявным объявлениям операций. Например:
package Ð is
type Ò is(À,Â);
procedure Q(X : in Ò, Y : out INTEGER);
end P;
...
type NEW_T is new T;
...

Именование и видимость (на примере Ады)
209
Тип NEW_T должен обладать свойствами, аналогичными всем свойствам типа Т.
В частности, должен иметь два перечисляемых литерала А и В (теперь уже типа
NEW_T) и операциюпроцедуру Р с параметрами
(X : in NEW_T, Y : out INTEGER).
Чтобы не заставлять программистов переписывать соответствующие объявле
ния (чем это плохо?) и вместе с тем соблюсти принцип обязательности объявле
ний, авторы Ады были вынуждены ввести так называемые неявные объявления.
Указанные выше литералы и процедура считаются объявленными неявно.
Вопрос. А зачем обязательность объявлений?
Подсказка. Для прогнозированияконтроля и, следовательно (по замыслу), повы
шения надежности.
Наконец, иногда оборачивается неприятными сюрпризами требование опре
деленного «комфорта» при написании программ. В результате возникает много
локальных неоднозначностей. Скажем, «А(I)» может обозначать элемент масси
ва, вызов функции или вырезку массива ((n–1)мерный подмассив nмерного
массива А).
Следующий пример взят из журнала Ada LETTERS. Неприятность
связана с неявными инициализирующими выражениями у входных параметров
функции и процедур.
procedure test is
type Enum is(Red, Green);
type Vec is array(Enum) of Enum;
X : Enum;
Y : Vec;
function F(A : Enum := Red) return Vec is
begin
return Y;
end;
begin
X := F(Red);
-- Что в последней строчке? Вызов функции с параметром RED или элемент массива,
-- вычисленного вызовом функции без параметров (ведь инициализированные
-- параметры можно опускать).
-- [Надо бы F()(Red), как в Фортране-77].
Y := F(Red); -- здесь тоже не ясно
-- следует учесть, что правилами перекрытия пользоваться некорректно –
-- функция одна, и перекрытия нет
end test;
Замечание. Конечно, так программировать нельзя независимо от свойств ЯП.
Программа не ребус. Ее нужно читать, а не разгадывать!
Еще хуже:
procedure F is
type ARR;

210
type ACC is access ARR;
type ARR is array(1..10) of ACC;
X : ACC;
function f(X : INTEGER := 0) return ACC is
begin
return new ARR;
end f;
begin
X := f(l); – допустимы две интерпретации
end;
Вопрос. Какие именно интерпретации?
Итак, требования к языку, которые в наибольшей степени повлияли на схему
идентификации в Аде, названы. Рассмотрим эту схему.
Современное состояние языков программирования
10.7. Схема идентификации
10.7.1. Виды объявлений в Аде
Изложенные ниже подробности имеют основной целью продемонстрировать от
носительную сложность идентификации в Аде, а не полностью описать ее или тем
более научить ею пользоваться. Поэтому читатель, для которого доказываемый
тезис очевиден или неинтересен, может без ущерба пропустить оставшуюся часть
главы 10.
Явные объявления. Кроме собственно объявлений, будем считать явными
объявлениями также части объявлений, синтаксически не выделяемых в отдель
ные конструктыобъявления, хотя содержательно играющие такую роль. Это
компоненты записей (в том числе и дискриминанты типа), входы задач, парамет
ры процедур и родовые параметры, перечисляемые литералы, параметр цикла.
Неявные объявления. Это имя блока, имя цикла, метка оператора, перечисля
емые литералы и унаследованные подпрограммы производных типов, предопре
деленные операции типов различных категорий. Перечисляемые литералы счита
ются неявно объявленными функциями без параметров.
Зачем нужны неявные объявления. Как уже не раз отмечалось, одним из важ
нейших требований к Аде было требование надежности, составной частью кото
рого является требование обнаруживать и диагностировать как можно больше
нарушений во время компиляции (до начала выполнения программы), то есть
требование статического прогнозирования и статического контроля. Это вклю
чает и контроль использования имен (идентификаторов), для чего и необходимо
прогнозирование, то есть тот или иной способ объявления.
Явно объявлять метки (как в Паскале) обременительно. С другой стороны,
метки могут конфликтовать с другими именами; чтобы контролировать такие
коллизии с учетом областей локализации, удобно считать метки объявленными
«рядом» с остальными (явно объявленными) именами рассматриваемой области
локализации.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
