В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 44
Текст из файла (страница 44)
Создать (Сеть2); (* с помощью импортированной процедуры *)
Вставить (33, 13, Сеть1);
...
Присвоить (Сеть1, Сеть2);
(* объекту, указанному Сеть2, присваивается значение объекта, указанного
Сеть1. См. реализующий модуль, экспортирующий тип Сети. *)
END ПостроениеСетей;
В этом модуле отражено уже упоминавшееся важное ограничение, касающееся
непрозрачных типов:
Объектами непрозрачных типов могут быть только ссылки (указатели) или
скалярные объекты.
Однако при использовании непрозрачных типов неизвестно, как они
устроены. Поэтому с ними можно выполнять лишь операции, явно
экспортированные соответствующим определяющим модулем. Именно поэтому по
сравнению с Ада-реализацией добавились операции Создать и Присвоить.
Так что непрозрачные типы Модулы-2 по использованию близки к
ограниченным приватным типам Ады.
12.5.4. Реализующий модуль
Ниже следует реализующий модуль (аналог тела пакета):
IMPLEMENTATION MODULE УправлениеСетями;
TYPE ЗаписьОбУзле = RECORD
Включен : BOOLEAN;
Связан : Связи;
END;
Сети = POINTER TO ARRAY Узел OF ЗаписьОбУзле;
(* описание устройства содержательных сетей *)
(* действует правило последовательного определения *)
PROCEDURE Создать (VAR Сеть : Сети);
BEGIN
Сеть := NEW Сети;
(* Работает генератор динамического объекта. Создается объект анонимного
регулярного типа (содержательная сеть) и указатель на этот объект. Созданный
указатель присваивается ссылочной переменной-параметру "Сеть". Обратите
внимание, в генераторе Модула-2 используется ссылочный тип, а не базовый,
как в Аде. Поэтому базовый вполне может оставаться анонимным. *)
END Создать;
PROCEDURE УзелЕсть (X : Узел; ВСети : Сети) : BOOLEAN;
BEGIN
RETURN ВСети^[X].Включен;
(* "^" означает так называемое разыменование - переход от имени к его
значению. Явное разыменование в Модуле-2 применяется только для объектов
ссылочных типов. "ВСети" означает массив, на который ссылается указатель
"ВСети". Квадратные скобки выделяют список индексов (алгольная традиция).
Точка имеет тот же смысл, что и в Аде. *)
END УзелЕсть;
PROCEDURE ВсеСвязи (X : Узел; ВСети : Сети) : Связи;
BEGIN
RETURN ВСети^[X].Связан;
END ВсеСвязи;
PROCEDURE Вставить (X : Узел; ВСеть : Сети);
BEGIN
ВСеть^[X].Включен := TRUE;
ВСеть^[X].Связан.Число := 0;
END Вставить;
PROCEDURE Присвоить (Сеть1, Сеть2 : Сети);
BEGIN
Сеть2^ := Сеть1^;
END Присвоить;
PROCEDURE Чистить (Связь, ВУзле : Узел; ВСети : Сети);
VAR i : 1..МаксСвязей; (* Переменную цикла нужно объявлять. При этом
контроль диапазона менее жесткий, чем в аналогичной А-программе, так как
граница отрезка типа обязана быть константным выражением. *)
BEGIN
FOR i := 1 TO ВСети^[ВУзле].Связан.Число DO
IF ВСети^[ВУзле].Связан.Узлы[i] = Связь THEN
Переписать (ВУзле, i, ВСети);
END (* условия *);
END (* цикла *);
END Чистить;
(* Мы сознательно программируем близко к соответствующей А-программе,
хотя можно было бы действовать рациональней *)
PROCEDURE Переписать (ВУзле : Узел;
После : ИндексУзла; ВСети : Сети);
VAR j :1..МаксСвязей;
BEGIN
WITH ВСети^[ВУзле].Связан DO (* присоединяющий оператор *)
FOR J := После TO Число-1 DO
Узлы[j] := Узлы [j+1];
END (* цикла *);
Число := Число-1;
END (* присоединяющего оператора *)
END Переписать;
(* Вместо переименования (которого нет) с успехом применен так называемый
присоединяющий оператор вида
WITH ИмяЗаписи DO Опереторы END
Его смысл в том, что между DO и END селекторы полей записи, указанной через
ИмяЗаписи, доступны по коротким именам. В нашем случае это селекторы "Число"
и "Узлы". Присоединяющий оператор имеется и в Паскале. *)
PROCEDURE Удалить (X : Узел; ИзСети : Сети);
VAR i : 1..МаксСвязей;
BEGIN
ИзСети^[X].Включен := FALSE;
FOR i := 1 TO ИзСети^[X].Связан.Число DO
Чистить (X, ИзСети^[X].Связан.Узлы[i], ИзСети);
END (* цикла *);
END Удалить;
PROCEDURE Есть_связь(АУзел, ВУзел : Узел, ВСети : Сети): BOOLEAN;
VAR i : 1..МаксСвязей;
BEGIN
WITH ВСети(АУзел).Связан DO
FOR i in 1..запись.число DO
IF Узлы(i) = BУзел THEN
RETURN TRUE ;
END;
END;
RETURN FALSE ;
END Есть_связь ;
PROCEDURE Установить_связь(Откуда, Куда : Узел; ВСети : Сети);
BEGIN
WITH ВСети(Откуда).Связан DO
Число := Число+1 ;
Узлы(Число) := Куда ;
END Установить_связь ;
PROCEDURE Связать (АУзел, ВУзел : Узел; ВСети : Сети);
BEGIN
if not Есть_связь(АУзел, ВУзел, ВСети) then
Установить_связь(АУзел, ВУзел, ВСети) ;
if АУзел /= ВУзел then
Установить_связь(ВУзел, АУзел) ;
end;
end;
END Связать ;
END УправлениеСетями;
(* Подчеркнем, что во внешнем контексте доступны только имена из списка
экспорта определяющего модуля. Реализующий модуль может включать лишь список
импорта (когда в нем используются внешние имена, которые не потребовались в
определяющем модуле. Как и в Аде, все имена, доступные в определяющем
модуле, доступны и в его реализующем модуле. *)
Итак, поставленная задача полностью решена. Обеспечена аналогичная А-
случаю целостность сетей, модифицируемость комплекса и надежность
программирования.
Вопрос. За счет чего?
Ответ. Непрозрачный тип "Сети", модульность (в частности, разделение
спецификации и реализации) и явные объявления (в частности, отрезки типов).
Основной вывод из нашего эксперимента : обычные программы можно писать
на Модуле-2 практически с тем же успехом и комфортом, что и на Аде.
Конечно, такие выводы не делают на основании одного эксперимента с
неотлаженным и тем более не применявшимся на практике комплексом программ.
Однако для наших целей важно, что при решении поставленной задачи мы ни разу
не попали в ситуацию, когда вместо А-средств не нашлись бы подходящие М-
возможности, не приводящие к необходимости кардинально перерабатывать
программу. При этом задача не подбиралась специально с учетом особенностей
Модулы-2, а была просто взята первая задача, на которой мы изучали
возможности Ады.
Накоплено достаточно сведений о Модуле-2, чтобы содержательно обсудить
принцип чемоданчика в сопоставлении с принципом сундука. Однако так как при
этом не обойтись без сопоставления А- и М-решений, уясним, в каком смысле
целесоообразно их сопоставлять, насколько это может быть правомерно и почему
поучительно.
Ключевые понятия при ответах на эти вопросы - языковая ниша и авторская
позиция. О втором уже шла речь, а первым займемся в следующем разделе.
12.6. Языковая ниша
Вопрос о том, насколько правомерно сравнивать Модулу-2 с Адой как
потенциальных конкурентов, тесно связан с понятием "языковая ниша".
Языковая ниша - это комплекс внешних условий, при которых активная
жизнь двух различных языков невозможна. Языковая ниша (или просто ниша)
характеризуется по меньшей мере классом пользователей ЯП, классом решаемых
задач (проблемной областью), классом инструментальных и целевых компьютеров
(точнее, программных сред, включающих компьютеры, операционные системы,
используемые прикладные пакеты и т.п.). На нишу, соответствующую ЯП, влияют
кроме собственных свойств ЯП, также и особенности технической и социальной
поддержки (наличие и качество реализаций, экономическая или иная
заинтересованность организаций, фирм, ведомств, стран и т.д.).
Сравнивать в качестве потенциальных конкурентов имеет смысл лишь ЯП,
претендующих на одну и ту же или пересекающиеся ниши. Иначе различия между
ЯП всегда можно объяснить различием "условий их активной жизни".
Однако если у конкретных ЯП сформировалась определенная ниша, то
бесперспективно говорить о его вытеснении потенциальным конкурентом. Опыт
показывает, что справедлив закон консерватизма ниш : ниша сопротивляется
замене ЯП. Этот же закон можно понимать как закон сохранения (обитателей)
ниш. Поэтому к ЯП (возможно даже более, чем к программам), применим
известный афоризм В.Л. Темова, который в применении к ЯП можно
перефразировать так: "Языки не внедряются, а выживают".
Так что мало смысла обсуждать, например, замену Фортрана или ПЛ/1 на
Аду или Модулу-2 без изменения класса используемых компьютеров, контингента
пользователей, решаемых задач и (или) других характеристик ниши.
Однако сравнивать в качестве потенциальных конкурентов Аду с Модулой-2
имеет смысл, хотя в первом приближении это ЯП различного назначения. Ада,
как уже говорилось, ориентирована в первую очередь на программирование
встроенных (встраиваемых) систем с применением кросс-компиляции. А Модула-2
- на задачи системного программирования на относительно бедных
однопроцессорных компьютерах.
Реальные языковые ниши для этих ЯП еще не сформировались. Они могут
оказаться конкурентами в ситуациях, когда принцип чемоданчика проявит
большую жизнеспособность, чем принцип сундука, даже подкрепленный мощной
поддержкой директивных органов.
Так, еще до появления доступных высококачественных реализаций Ады
Модула-2 может "захватить" классы применений, где ресурсы целевого
компьютера позволяют отказаться от кросс-компиляции, если сам компилятор
достаточно компактен. Подробное сопоставление потенциальных ниш Ады и
Модулы-2 выходит за рамки книги.
Однако особенно поучительно сравнивать проектные решения с авторской
позиции. В этом случае проектируемые ЯП могут быть предназначены для
совершенно разных языковых ниш. Желательно лишь, чтобы сопоставляющий по
возможности четко представлял себе особенности этих ниш и их связь с
принимаемыми проектными решениями.
При таком подходе принцип чемоданчика как авторский принцип может
проявиться в том, чтобы отказаться от борьбы за определенную нишу, если для
этого не созрели технические или социальные условия. Другими словами,
принцип минимальности можно интерпретировать и так, что следует выбирать
ниши, где предлагаемые языковые решения будут выглядеть как совершенно
необходимые.
Итак, будем сравнивать А- и М-решения прежде всего с авторской позиции.
Однако учтем, что возможно и пересечение соответствующих ниш.
12.7. Принцип чемоданчика в проектных решениях ЯП Модула-2
12.7.1. Видимость
Занимаясь управлением видимостью в Аде, мы обнаружили, во-первых,
технологические потребности, которые привели к созданию соответствующего А-
аппарата (пакетов, блоков, указателей контекста и сокращений, правил
перекрытия и наследования операций производных типов, переименования,
неявных объявлений). Во-вторых, были продемонстрированы проблемы, вызываемые
нежелательным взаимодействием многочисленных компонент этого сложного
аппарата. В частности, было показано, как неявные объявления в сочетании с
указателем сокращений могут приводить к неожиданным последствиям, явно
входящим в противоречие с основной целью ЯП - обеспечить надежное
программирование.
Как должен поступить автор ЯП, руководствующийся принципом чемоданчика?
Он должен заново проанализировать потребности и поискать компромисс, в
максимальной степени удовлетворяющий критические потребности при минимальной
возможной сложности предлагаемого языкового аппарата. Ключевая идея такого
компромисса, найденная Виртом для Модулы-2 - полный отказ от любых косвенных
(неявных) объявлений.
Компромисс состоит в том, что в первом приближении такая идея должна
быть не слишком удобной для пишущего программу. А иногда и для читающего.
Ведь появляются (потенциально довольно длинные) списки экспортируемых и
импортируемых имен. Их нужно писать, читать, проверять (и хранить). К тому
же по ним все равно невозможно узнать свойства именуемых сущностей и
приходится как читателю, так и транслятору анализировать экспортирующие
модули. Однако потому это и компромисс, что зато становятся совершенно
тривиальными правила видимости и контроля имен - ведь все они объявлены явно
в каждом модуле.
Вместе с тем этот компромисс следует именно принципу чемоданчика. Для
обеспечения раздельной компиляции совершенно необходимо управлять видимостью
имен, смысл которых определен в других модулях. Применяем простейший способ
- явное перечисление нужных имен с указанием модуля-экспортера. Получаем
списки импорта. Для надежности, облегчения понимания и модификации применяем
двойственный конструкт - списки экспорта. Получаем логически завершенный
аппарат связывания модулей.
У принятого решения - несколько важных следствий.
Во-первых, экспорт-импорт становится точнее, чем в Аде, где из-за
правила последовательного определения видимыми могут оказаться
несущественные для внешнего контекста, по сути промежуточные имена.
Во-вторых, концепция явных объявлений несовместима с концепцией
производных типов (с естественными для них неявными определениями операций).
Весьма вероятно, что именно отмеченная несовместимость - одна из причин
отсутствия производных типов в Модуле-2. А ведь это целый языковый пласт.
В-третьих, отказ от производных типов не позволяет применять типы в
качестве характеристики содержательной роли объекта, что меняет саму
концепцию типа. Такого рода последствия можно обнаруживать и дальше.
С одной стороны, они подчеркивают взаимозависимость и
взаимообусловленность компонент ЯП как знаковой системы. С другой стороны,