В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 69
Текст из файла (страница 69)
решение (с нетривиальными последствиями).
Верна и обратная лемма: концепция типов языка Ада требует от
подпрограммных типов явного объявления (как самого типа, так и
принадлежности каждой подпрограммы этому типу).
Действительно, тип параметра (любого, в том числе и подпрограммного
типа) в Аде можно задать только посредством имени типа этого параметра. И
если этот тип объявлен, например, как
procedure type P is (X : real);
а некоторый параметр (или переменная) специфицирован как
V : P;
то в общем случае конкретная процедура Q со спецификацией
procedure Q (X : real);
несовместима с V, если не указано явно, что она имеет процедурный тип P.
Скажем, так
Q : constant P is < тело >;
Сравните с Модулой-2, где Q совместима с V в силу структурной
эквивалентности подпрограммных типов (явного типа P и анонимного типа
процедуры Q). Но в Аде концепция типа - чисто именная. Обратная лемма
доказана.
Итак, хотя для обычных процедур явная привязка к определенному типу
выглядит обременительной (нужно придумать название типа и указать с ним
связь при объявлении процедуры), концепция подпрограммных типов в целом (с
явно именуемыми процедурными типами) вполне укладывается в типовую идеологию
Ады.
Даже идея типа как содержательной характеристики роли объекта (в нашем
случае - процедуры) проходит полностью.
Например
procedure type P is (x : real);
Q1 : constant P is < тело1 >;
Q2 : constant P is < тело2 >;
Q3 : constant P is < тело3 >;
procedure type P1 is new P; -- новая содержательная роль
procedure type P2 is new P; -- еще одна
17.13.3. Родовые подпрограммные параметры
С другой стороны, в Аде имеются родовые параметры (параметры периода
компиляции) и подпрограммы могут выступать в их роли. Необходимость в таких
параметрах диктуется следующими соображениями.
1. Тип в Аде не может быть динамическим параметром - это очевидным
образом противоречит концепции статического контроля типа. Например
procedure P(T1,T2) is -- пусть T1 и T2 - параметры-типы
a : T1;
b : T2;
a := b; -- допустимо ли? Зависит от параметров
2. Потребность в настройке есть. Например
package стек is
втолкнуть (X : in T);
вытолкнуть (X : out T);
end стек;
Нужно сделать тип параметром! Однако из-за (а) - только статическим!
3. Но тип в Аде - это класс значений вместе с классом операций.
Поэтому операции (подпрограмммы) тоже должны быть параметрами (хотя бы
статическими). Очередная неформальная теорема о необходимости в Аде родовых
подпрограммных параметров доказана.
[Хотелось бы сделать это экономно и надежно. Для этого следует:
1) обеспечить такой же уровень локального статического контроля
модулей, как и без родовых параметров;
2) обеспечить возможность не перетранслировать родовые модули при
каждой настройке на фактические родовые параметры.
Уже требование (1) приводит фактически к эквиваленту структурного
подпрограммного типа, действующего в рамках родового млдуля - его роль в Аде
играет спецификация родового процедурного параметра (см. примеры в разделе
"Родовые сегменты").
Упражнение. Обоснуйте последнее утверждение. ]
17.13.4. Почему же в Аде нет подпрограммных типов?
Основной наш тезис: их нет потому, что ее авторы посчитали
соответствующие технологические потребности удовлетворенными за счет родовых
подпрограммных параметров (которые, как только что показано, в Аде все равно
нужны), а также задачных типов.
Действительно, зависимость от статически известных подпрограмм-
индивидов полностью покрывается родовыми параметрами. Если не видеть
потребности во входовых параметрах (для которых аргументы как раз могут
возникать и динамически,, да и при статическом их порождении не все они
известны при написании программы, как показывают наши примеры с
преобразованием координат), то не стоит вводить подпрограммные типы (так
как это утяжеляет средства определения и контроля типов - концепцию типа в
целом).
С другой стороны, все связанные с подпрограммными типами проблемы
контроля - от наличия у подпрограмм динамических параметров. На самом деле
экземпляр подпрограммы определяет целый класс (т.е. тип) ее запусков, а
подпрограммный тип - это тип типов запусков.
С этой точки зрения в Адовских задачах произведено РАЗДЕЛЕНИЕ
ПАРАМЕТРОВ - динамические параметры сконцентрированы в ее входах, а сама
задача (и задачный тип) может иметь только статические параметры. Так что
задача соответствует всего одному запуску, а задачный тип (ведь он без
динамических параметров и все запуски аналогичны) - типу запусков. В этом (
в уровне абстракции) и состоит фундаментальное отличие подпрограммного и
задачного типа, объясняющее разное отношение к ним авторов Ады.
Имя входа фактически служит именем подпрограммного типа (типа
операторов приема, играющих роль процедурных констант). Этот тип локален в
задаче.
Таким образом, можно заключить, что подпрограммных типов в Аде нет,
потому что соответствующие потребности удовлетворены частично через задачи,
частично через спецификации родовых параметров. Образно говоря, их нет,
потому что они на самом деле есть.
17.13.5. Еще о входовых типах
Итак, мы обнаружили потребность в обобщении "локальных" подпрограммных
типов, косвенно имеющихся в Аде в качестве имен входов, и предложили
соответствующие выразительные средства. Без такого обобщения в Аде
динамическими подпрограммными параметрами могут служить только имена входов
(через задачи, как в нашем примере с преобразованием координат), а
статическими - и обычные процедуры.
Чем же входовые типы лучше, чем произвольные подпрограммные?
Неформальная теорема: в Аде для согласованного с ее "духом" введения
подпрограммного типа нужно изменить привычный способ записи процедур.
Действительно, процедуру следовало бы записывать так
P : constant Proc is тело;
где Proc - подпрограммный (процедурный) тип. К тому же этот тип
потребовалось бы вводить заново по крайней мере для каждого профиля! И все
ради чего? Только ради того контроля, который при необходимости для
статически известных процедур в Аде выполняется при родовой настройке.
Правда, для этого приходится изменять именной контроль типов на
структурный контроль родовых спецификаций, но здесь это хорошо, так как
именно такой контроль и нужен по делу.
Обратите внимание, в Модуле-2 процедуры могут взаимодействовать как
сопрограммы! Для этого и нужен процедурный тип - можно размножать процедуры
динамически и моделировать взаимодействие процессов. Ведь иных средств для
этого в Модуле-2, в отличие от Ады, нет. А в Аде как раз нет сопрограмм и
изложенные соображения не действуют.
Можно заключить, что когда подпрограммный тип не связан с естественным
размножением "тел" подпрограмм (как в случае входов в Аде или сопрограмм в
Модуле-2), его появление в ЯП с Адовской концепцией типа не согласуется с
его духом (нарушает его концептуальную целостность). По существу получается
совершенно другой язык. А именно, в таком ЯП придется полностью отделить
спецификацию подпрограммного типа от объявления объекта этого типа (т.е.
процедуры или функции - константы или переменной), а также от присваивания
ему значения (аналога тела процедуры).
Такое совершенно непривычно для Ады в целом, но полностью согласуется с
использованием входов - достаточно объявление входа трактовать как
объявление константы входового типа, известного и вне задачи (задачного
типа).
17.13.6. Заключительные замечания
Подведем итог, частично другими словами повторив сказанное выше.
1. Однородные процессы, играющие аналогичные роли - естественны.
Контроль типов полностью статический, хотя объектов может быть неограниченно
много.
2. Однородные подпрограммы (с одинаковыми профилями) при отсутствии
возможности динамически создавать подпрограммы остаются в ограниченном
количестве и можно настраиваться статически. Почти всегда достаточен аппарат
родовых пакетов (хотя мог бы быть полезен аппарат репликаторов по примеру
Оккама).
3. Динамическое создание содержательно различных подпрограммы не
соответствует идее концентрации контроля на богатой инструментальной машине.
Если пойти по этому пути, придется мощную систему контроля иметь на бедной
целевой машине - не соответсвует требованиям к Аде.
4. Коллективы асинхронных процессов полезны именно потому, что их члены
взаимодействуют, работая "одновременно". Тип таких процессов естественно
называть одним именем как в силу их аналогичного назначения, так и
идентичного устройства. Важно также, что идентификация объектов задачного
типа - необходимое условие их коллективного взаимодействия. При этом она не
может быть непосредственно связана со статической структурой программы
(примеры см. выше).
5. Взаимодействие вызовов подпрограмм возможно только по исходным
данным и результатам, так как исполнение одного вызова исключает исполнение
другого (случай рекурсивных вызовов - спорный). Передача доступа к таком
объекту в качестве (динамического) параметра невозможна.
6. Для обычных процедур привязка к определенному типу не только
обременительна (нужно придумать название типа и явно указать с ним связь при
объявлении процедуры), но в привычном Aдовском виде плохо согласуется с
Aдовской же идеей типа как характеристики содержательной роли значений этого
типа.
Действительно, если ввести тип P1 как имя типа для процедурного
параметра V1, а имя P2 для типа параметра V2, т.е.
V1 : P1; V2 : P2;
и по каким-то причинам хотеть, чтобы из набора конкретных процедур Q1, Q2, и
Q3 вместо V1 можно было подставлять Q1 и Q2, а вместо V2 - Q2 и Q3, то
пришлось бы дублировать тело Q2 - для объявления и с типом P1, и с типом P2.
7. Вот пример воплощения средствами Ады параметрической интегрирующей
функции
generic
with function f (X : in real) return real;
function интеграл (A,B : in real) return real is
. . .
end интеграл;
function инт_f1 is new интеграл (f1);
8. Глубинное различие задачи и подпрограммы в том, что если асинхронный
процесс естественно понимать как экземпляр (объект) некоторого (задачного!)
типа аналогичных объектов, то подпрограмма - это целый класс (тип) отдельных
запусков. Именно отдельные запуски естественно считать экземплярами
(объектами) подпрограммного типа.
Итак, подпрограммный тип - это тип типов вызовов, задачный тип - тип
вызовов. Другими словами, асинхронный процесс и подпрограмма различаются
уровнем абстракции. Поэтому настраиваться на процесс существенно проще, чем
на подпрограмму. А именно следует учитывать, что у фиксированной
подпрограммы остаются явные (динамические) параметры, что сама она
фактически является типом (запусков), в отличие от асинхронного процесса.
Следовательно, в общем случае допуск процедур-параметров по сути
означает допуск типов-параметров. Однако это уже очевидным образом
противоречит статической концепции строгой типизации (почему?) принятой в
Аде (и Паскале).
9. Но из этого затруднения авторы Ады и Паскаля выходят по-разному.
Сказывается, во-первых, ориентация на чисто именную совместимость типов
в Аде и на частично-структурную - в Паскале. Действительно, в случае, когда
профиль процедур фиксирован, вызовы различных процедур одного профиля легко
укладываются в схему статического контроля типов (ведь именно профиль
следует сопоставлять с типами фактических параметров и вырабатываемого
результата параметрической процедуры или функции).
Поэтому для Паскаля с его структурной концепцией типа естественно
принять, что подпрограммы (процедуры и функции в терминах Паскаля) с одним
профилем принадлежат одному типу (что не требует обязательных дополнительных
указаний со стороны программиста).
В Аде подобное допущение было бы неестественным, так как нарушило бы
чисто именную концепцию типа.
Во-вторых, следует учитывать отсутствие в Паскале иных возможностей
настройки на процедурные параметры (важно понимать, что совсем без нее не