В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 38
Текст из файла (страница 38)
Относительная независимость именования внутри сегментов. Программисты,
работающие над одной программой, не должны быть жестко связаны дисциплиной
выбора имен (на нижних уровнях иерархии программы). Следствие - блочная
структура областей локализации имен. Областью локализации называется
фрагмент программы, в котором могут быть введены имена, непосредственно
доступные в этом фрагменте и непосредственно недоступные извне этого
фрагмента. Таким образом, понятие области локализации позволяет
структурировать "пространство имен" программы.
Необходимость переименования и сокращения длинных имен.
Критичность проблемы полиморфизма. Следствие: потребовалось заменить
классический (бытовавший в ЯП еще со времен Алгола-60) запрет объявлять
одинаковые идентификаторы в одном блоке более гибким ограничением,
позволяющим объявлять одноименные операции, процедуры м функции с
различающимися профилями.
Принцип обязательности объявлений для всех имен (кроме
предопределенных).
Необходимость производных типов. Следствие: неявные объявления
операций.
Пример:
package P is
type T is (A,B);
procedure Q(X : in T, Y : out INTEGER);
end P;
...
type NEW_T is new T;
...
Тип NEW_T должен обладать свойствами, аналогичными всем свойствам типа
T. В частности, должен иметь два перечисляемых литерала A и B (теперь уже
типа NEW_T) и операцию-процедуру P с параметрами
(X : in NEW_T, Y : out INTEGER ).
Чтобы не заставлять программистов переписывать соответствующие
объявления (чем это плохо?) и вместе с тем соблюсти принцип обязательности
объявлений, авторы Ады были вынуждены ввести так называемые неявные
объявления. Указанные выше литералы и процедура считаются объявленными
неявно.
Вопрос. Зачем обязательность объявлений?
Подсказка. Для прогнозирования-контроля и, следовательно (по замыслу)
повышения надежности.
Требование определенного "комфорта" при написании программ. Иногда оно
оборачивается неприятными сюрпризами. Два примера:
1. Много локальных неоднозначностей. Скажем "A(I)" может обозначать
элемент массива; вызов функции; вырезку массива (n-1-мерный подмассив n-
мерного массива A).
2. Сложности и неожиданные ошибки из-за неявных инициализирующих
выражений у входных параметров функции и процедур. Вот пример, взятый из
журнала 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;
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 ;
begin
X := f(1); -- допустимы две различные интерпретации
end;
Вопрос. Какие именно интерпретации?
Итак, требования, которые в наибольшей степени повлияли на схему
идентификации в Аде, названы. Рассмотрим эту схему.
10.7. Схема идентификации
10.7.1. Виды объявлений в Аде:
Изложенные ниже подробности имеют основной целью продемонстрировать
относительную сложность идентификации в Аде, а не полностью описать ее или
тем более научить ею пользоваться. Поэтому читатель, для которого
доказываемый тезис очевиден или неинтересен, может без ущерба пропустить
оставшуюся часть раздела 10.
явные объявления (будем считать, что с ними все ясно); части явных
объявлений, синтаксически не выделяемых в отдельные конструкты-объявления,
хотя содержательно играющие такую роль: компоненты записей (в том числе и
дискриминанты типа), входы задач, параметры процедур и родовые параметры,
перечисляемые литералы, параметр цикла;
неявные объявления: имя блока; имя цикла; метка оператора;
перечисляемые литералы; унаследованные подпрограммы производных типов;
предопределенные операции типов различных категорий.
Заметим, что перечисляемые литералы считаются неявно объявленными
функциями без параметров.
Зачем нужны неявные объявления. Как уже не раз отмечалось, одним из
важнейших требований к Аде было требование надежности, составной частью
которого является требование обнаруживать и диагностировать как можно больше
нарушений во время компиляции (до начала выполнения программы), т.е.
требование статического прогнозирования и статического контроля. Это
включает и контроль использования имен (идентификаторов), для чего
необходимо прогнозирование, т.е. тот или иной способ объявления.
Явно объявлять метки (как в Паскале) все-таки обременительно. С другой
стороны метки могут конфликтовать с другими именами; чтобы контролировать
такие коллизии с учетом областей локализации, удобно считать метки
объявленными "рядом" с остальными (явно объявленными) именами
рассматриваемой области локализации.
Например, метки операторов считаются неявно предобъявленными
непосредственно после всех явных объявлений соответствующей области
локализации и тем самым действуют в пределах всей этой области.
Неявные объявления унаследованных подпрограмм. Основные неприятности
возникают из-за неявных объявлений унаследованных подпрограмм при объявлении
производных типов.
По замыслу производного типа в момент своего объявления он должен
сохранить все те свойства (кроме имени), которые заданы в определяющем
пакете родительского типа. Следовательно, вместе с новым типом должны
считаться объявленными и подпрограммы, унаследованные от родительского типа.
Но объявленными где? Ведь явных объявлений этих подпрограмм для нового типа
нет, а от точного места объявлений зависит и смысл программы, и результаты
контроля.
Упражнение. Приведите примеры такой зависимости.
В Аде эта проблема решается так: все унаследованные подпрограммы
считаются неявно объявленными сразу вслед за объявлением производного типа.
Эти неявные объявления "уравнены в правах" с явными объявлениями.
Далее, если родительский тип был перечисляемым, то производный должен
иметь те же перечисляемые литералы; поэтому их тоже считают объявленными
неявно (в качестве функций без параметров).
Вопрос. Причем эдесь функции, да еще без параметров?
Подсказка. Эти литералы могут перекрываться, поэтому их удобно считать
функциями для единообразия правил перекрытия.
Вопрос. А почему можно считать литералы функциями?
Другие особенности механизма объявлений.
1. Механизм наследования во взаимодействии с указателем сокращений и
перекрытием оказывается довольно сложным. Подробнее об этом будет сказано в
связи с проблемой сокращений. Основная идея: наследуются только подпрограммы
из определяющего пакета родительского типа, причем (важно!) наследование
возможно только извне пакета.
2. Формальная область действия неявного объявления метки может
оказаться существенно шире, чем та часть текста, из которой на эту метку
можно ссылаться. Например, запрещено передавать управление внутрь цикла,
хотя формально внутренняя метка цикла может считаться неявно объявленной вне
цикла, после явных объявлений внешней области локализации.
3. Наравне с идентификаторами объявляются строки - знаки операций и
символьные константы.
Устройство полных (составных) имен. Общая структура имени такова :
_________ . ________ . ... . ________
нечто нечто нечто
без точки без точки без точки
Причем нечто_без_точки - это
идентификатор нечто_в_скобках ... нечто_в_скобках
В скобках записаны либо индексы массива, либо аргументы вызова функции.
Заметим, что в Аде возможен элемент массива вида a(i)(j)...
Рассмотрим три примера :
procedure P is -----------------
type T is (A,B,C); !
type T1 is array (1..10) of T !
type T2 is record !
A2 : T1; !
B2 : T1; !
end record !
type T3 is array (1..10) of T2; -- Массив записей !
-- сложной структуры !
X : T3; !
begin !
X(2).A2(3):=C; !
end; ----------------
или
procedure Q is ----------------
package P1 IS -- Способ "достать" !
package P2 is -- из пакета нужный !
type T is (A,B); -- ресурс !
end P2; !
end P1; !
X : P1.P2.T; !
... -----------------
или
procedure P is
I : INTEGER ;
...
procedure P1 is
I : INTEGER ;
...
begin
...
P.I:= P1.I;
P1.I:= P.P1.I+1; -- эквивалентно I := I+1; (*)
end P1;
...
end P;
Последний пример одновременно демонстрирует и мощь средств именования в
Аде. В традиционных ЯП с блочной структурой имя, объявленное во внутренней
области локализации, закрывает все глобальные омонимы. Другими словами,
одноименные объекты в такой области локализации абсолютно недоступны.
В общем случае это не всегда удобно, иногда полезно иметь доступ к
закрытому объекту (приведите примеры, когда это может понадобиться!).
В Аде достаточно указать полное имя закрытого объекта. Но для этого
необходимо иметь возможность называть именами области локализации. Поэтому в
Аде появились именованные блоки и циклы. [В цикле с параметром объявляется
параметр цикла; имя цикла применяется в операторе выхода из цикла (exit)].
Упражнение. Приведите примеры применения полных имен для доступа к
закрытым локальным переменным блока, к закрытым параметрам цикла, для выхода
из вложенного цикла. Воспользуйтесь при необходимости каким-либо учебником
по программированию на Аде.
Вопрос. Как Вы думаете, разрешен ли в Аде доступ по полным именам извне
внутрь области локализации. Постарайтесь ответить, не пользуясь руководством
по Аде, опираясь только на свое понимание общих принципов этого ЯП.
Итак, денотат полного имени получается последовательным уточнением при
движении по составному имени слева направо.
Применение составных имен. Составное имя может использоваться в
следующих случаях:
1. Именуемая компонента:
компонента объекта комбинированного типа;
вход задачи;
объект, обозначаемый ссылочным значением.
2. Полное имя:
объект, объявленный в видимой части пакета;
объект, объявленный в охватывающей области локализации.
Источники сложности. В именуемой компоненте:
1. указанные пять случаев с точки зрения контекстно-свободного
синтаксиса не различаются.
2. в одном и том же полном имени может комбинироваться несколько
случаев.
Пример:
procedure P is
package Q is
type T is
record
A:INTEGER ;
B:BOOLEAN;
end record ;
X:T;
end Q
begin
...
Q.X.A:=1;
end P;
10.7.3. Области локализации и "пространство имен" Ада-программы
Как уже сказано, областью локализации называется фрагмент программы, в
котором введены имена, непосредственно доступные в этом фрагменте и
непосредственно недоступные вне этого фрагмента.
В Аде имеются следующие разновидности областей локализации :
программный модуль (спецификация плюс тело);
объявление входа вместе с соответствующими операторами приема входа
(вводятся имена формальных параметров);
объявление комбинированного типа (вводятся имена полей) вместе с
соответствующим возможным неполным объявлением или объявлением приватного
типа (вводятся дискриминанты), а также спецификацией представления;
переименование (воэможно, вводятся новые имена формальных параметров
для новых имен подпрограмм);
блок и цикл.
Область локализации физически не обязана быть связным фрагментом. В
частности, возможны области локализации, состоящие из нескольких
компилируемых модулей.
Омографы и правила перекрытия. Отличительные особенности областей
локализации в Аде - применение полных имен для доступа к непосредственно
невидимым объектам и применение перекрытия для борьбы с коллизией имен.
Примеры первого уже были. Займемся подробнее вторым.
В Аде разрешено перекрывать имена операций и процедур (включая
предопределенные операции, такие, как +, -, *, and), перечисляемые литералы
(поэтому-то они и считаются неявно объявленными функциями без параметров) и