В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 10
Текст из файла (страница 10)
этих ЯП не имеет.
В строках 16-17 - ОБЪЯВЛЕНИЯ ФУНКЦИЙ. Отличаются от процедур ключевым
словом function (а не procedure) и указанием типа результата (после return).
Режим параметров не указывается, потому что для функций всегда
подразумевается режим in (все параметры функций - только входные, т.е.
функции не могут менять значения своих аргументов).
Обратите внимание, в спецификации пакета указаны лишь спецификации
(заголовки) процедур и функций. В таком случае их тела следует поместить в
ТЕЛО ПАКЕТА, о котором пойдет речь в следующем разделе.
На этом закончим предварительное знакомство с Ада-конструктами.
2.5. Как пользоваться пакетом управление_сетью
Пусть нужно построить сеть из пяти узлов (13, 33, 25, 50, 90) и шести
дуг (13, 33), (33, 25), (33, 50), (33, 90), (13, 50) и (25, 90). [Нарисуйте
такую сеть].
Это можно сделать следующей процедурой построение_сети.
1. with управление_сетью ;
2. use управление_сетью ;
3. procedure построение_сети is
4. begin
5. вставить(33) ;
6. вставить(13) ;
7. связать(33, 13) ;
8. вставить(25) ; связать(33,25) ;
9. вставить(50) ; вставить(90) ; связать(33,50) ;
10. связать(33,90) ; связать(13,50) ; связать(25,90) ;
11. end построение_сети ;
Первые две строки позволяют пользоваться услугами, предоставляемыми
пакетом управление_сетью так, как будто все услуги объявлены непосредственно
перед третьей строкой.
Как уже сказано, строка с ключевым словом with называется УКАЗАНИЕМ
КОНТЕКСТА (with clause). Указание контекста делает видимыми (доступными по
ПОЛНЫМ именам) все услуги, объявленные в пакетах, перечисленных вслед за
with. Например, к процедуре "вставить" можно было бы обратиться так:
управление_сетью.вставить(...);
а объявить переменную типа "связи" можно так:
А : управление_ сетью.связи;
Строка с ключевым словом use называется УКАЗАНИЕМ СОКРАЩЕНИЙ (use
clause). Это указание позволяет пользоваться видимыми именами, не предваряя
их именем пакета. Так мы и поступили в процедуре построение_сети.
Подчеркнем, что указание сокращений действует только для уже видимых имен.
Его обязательно нужно предварять указанием контекста.
Если нужно напечатать сведения о построенной сети, то это можно сделать
следующими операторами (будем считать, что предопределены процедуры
новая_строка (переход на новую строку при печати) и "печать" (целого числа
или массива )).
новая_строка ;
for i in узел loop
if узел_есть(i) then
печать(i) ;
печать(все_связи(i).узлы) ;
end if;
новая_строка;
end loop;
Будет напечатано:
13 33 50
25 33 90
33 13 25 50 90
50 33 13
90 33 25
Обратите внимание: тип "узел" используется для указания диапазона
изменения значений переменной цикла. В нашем случае тело цикла выполнится
100 раз.
Третий шаг детализации - тело пакета. До сих пор мы смотрели на наш
комплекс услуг с точки зрения потенциального пользователя. Теперь настало
время реалиэовать те услуги, которые мы объявили в спецификации пакета. В
терминах Ады это означает, что нужно спроектировать ТЕЛО ПАКЕТА
управление_сетью. Создавать тело пакета будем также пошаговой детализацией.
Шаг 3.1. Неважно, с детализации какой процедуры или функции начинать -
ведь ни одну из них нельзя написать прежде, чем не станет понятно, как
представлена сама сеть, с которой нужно работать. Поэтому начнем с
проектирования представления данных. Займемся представлением сети.
Есть много вариантов такого представления (таблица, список,
перемешанная таблица и т.п.). Выберем представление сети массивом.
сеть : array (узел) of запись_об_узле ;
Мы написали ОБЪЯВЛЕНИЕ ОБЪЕКТА. Как всякое объявление объекта, оно
связывает имя ("сеть") с характеристиками того объекта данных, который в
дальнейшем будет значением (денотатом) объявленного имени. В нашем случае
этот объект - одномерный массив с компонентами типа запись_об_узле,
доступными по индексам типа узел.
Шаг 3.2. Следует заняться уточнением того, как устроен объект типа
запись_об_узле. Естественно считать, что это некоторая структура данных,
куда вносятся сведения о том, включен ли узел в сеть и если да, то какие
узлы с ним связаны. Объявим тип запись_об_узле.
type запись_об_узле is
record
включен : BOOLEAN := false;
связан : связи ;
end record;
Итак, каждая запись об узле состоит из двух полей. Поле с именем
"включен" и начальным значанием false служит признаком включения узла в
сеть, а поле с именем "связан" содержит все связи узла.
Шаг 3.3. Теперь все готово, чтобы заняться операциями над сетью.
Начнем с функции узел_есть.
Уточним ее внешний эффект: она должна быть применима к любому объекту
типа узел и должна выдавать результат true, если узел с таким именем есть в
сети и false в противном случае.
Мы сформулировали ее содержательный эффект. Такого рода сведения о
функции узел_есть должны быть в пользовательской документации. Это
необходимое для пользователя дополнение к спецификации (заголовку функции),
указанной в спецификации пакета в строке 18. Но сейчас нас интересует
реализация функции. Поэтому следует обеспечить ее содержательный эффект в
терминах реализационных, в частности, через представление сети (которое
пользователю недоступно и даже может оказаться неизвестным). Было бы
естественным выдавать в качестве результата просто значение поля "включен"
записи об узле. Но для этого на всю остальную реализацию пакета необходимо
наложить единое требование (если угодно, определить дисциплину работы с этим
полем): его значением в любой компоненте массива "сеть" после выполнения
любого действия должно быть true, если узел есть в сети, и false в противном
случае. При выполнении этого требования необходимый содержательный внешний
эффект функции узел_есть обеспечивается следующим объявлением
(определением):
function узел_есть(X : узел) return BOOLEAN is
begin
return сеть(X).включен ;
end узел_есть ;
[Обратите внимание: в полном определении функции повторена ее
спецификация.]
ОПЕРАТОР ВОЗВРАТА (return) завершает исполнение тела функции, доставляя
в качестве ее результата значение указанного выражения. В нашем случае это
ВЫБОРКА (поля "включен" из записи, находящейся в массиве "сеть" по индексу,
указываемому значением формального параметра "X").
Шаг 3.4. Займемся реализацией функции все_связи. Содержательный
внешний эффект - проявление связей узла. При соответствующей дисциплине
работы с сетью ее реализация могла бы быть такой:
function все_связи(X : узел) return связи is
begin
return сеть(X).связан ;
end все_связи ;
[Вопрос. В чем должна состоять требуемая дисциплина?]
К такой функции можно обращаться лишь тогда, когда известно, что узел в
сети есть, иначе можно выбрать неопределенное значение в поле "связан".
Шаг 3.5. Реализация процедуры "вставить" (с очевидным содержательным
эффектом) может выглядеть так:
procedure вставить(X : in узел) is
begin
сеть(X).включен := true ;
сеть(X).связан.число := 0 ;
end вставить ;
Теперь займемся процедурами "удалить" и "связать". Они чуть сложнее за
счет того, что нужно вносить изменения в несколько компонент массива "сеть".
Шаг 3.6. Содержательный эффект процедуры "удалить" очевиден: узел с
указанным именем должен быть удален из сети и, с учетом требования
поддерживать целостность сети, все связи, в которых он участвовал, должны
быть ликвидированы.
Такого содержательного эффекта можно достичь многими способами. Здесь
естественно воспользоваться тем, что пользователи лишены возможности
непосредственно изменять "сеть" (например, явными присваиваниями этому
массиву), они могут к нему добираться только посредством объявленных в
спецификации пакета процедур и функций. Наша задача как реализаторов пакета
- обеспечить согласованный внешний эффект объявленных услуг (при этом
внутренний эффект процедур и функций можно варьировать).
Другими словами, действие процедуры "удалить" на массив "сеть" должно
быть таким, чтобы функции узел_есть и все_связи выдали результаты,
согласованные с содержательным представлением об отсутствии узла в сети.
Один вариант реализации - присвоить false соответствующему полю "включен" и
подправить поле "связан" во всех узлах, с которыми был связан удаляемый
узел. Другой вариант - в этой процедуре поле "связан" не подправлять, но
изменить реализацию функции все_связи так, чтобы перед выдачей результата
она приводила поле "связан" в соответствие с полем "включен".
[Это и есть варианты упоминавшихся выше дисциплин работы с сетью.]
Рациональность выбора одного из вариантов не очевидна. Если часто
удаляют узлы и редко просят все их связи, может оказаться выгодным второй
вариант, иначе - первый. Оценить частоту запросов дело непростое. Поэтому
возможность менять реализацию (подстраиваясь к условиям эксплуатации), не
меняя внешнего эффекта, может оказаться очень важной.
[Обратите внимание: Не спроектировав представления данных, мы не могли
начать проектировать процедуры. А теперь видим, что проектирование данных
может зависеть от дисциплины взаимодействия операций. В этом - одно из
проявлений принципа единства основных абстракций, о котором мы еще
поговорим].
Выберем первый вариант реализации.
procedure удалить(X : in узел) is
begin
сеть(X).включен := false;
for i in 1..сеть(X).связан.число loop
чистить(X,сеть(X).связан.узлы(i)) ;
end loop ;
end ;
Понадобилась процедура "чистить", которая должна убрать в узле,
указанном вторым параметром, связь с узлом, указанным первым параметром.
procedure чистить(связь : узел, в_узле : узел) is
begin
for i in 1..сеть(в_узле).связан.число loop
if сеть(в_узле).связан.узлы(i) = связь then
переписать(в_узле, после => i) ;
end if ;
end loop ;
end чистить ;
Осталось спроектировать процедуру "переписать" - она должна переписать
связи в указанном узле, начиная с номера "после", и уменьшить на единицу
общее число связей этого узла.
procedure переписать(в_узле : in узел, после : in индекс_узла) is
запись:связи renames сеть(в_узле).связан ;
begin
запись.число := запись.число - 1 ;
for j in после..запись.число loop
запись.узлы(j) := запись.узлы(j+1) ;
end loop ;
end переписать ;
Здесь мы впервые воспользовались ОБЪЯВЛЕНИЕМ ПЕРЕИМЕНОВАНИЯ, чтобы
сократить имена и сделать их более наглядными. Этот же прием можно было
применять и раньше. Напомним, что о диагностике ошибок мы пока не заботимся
(предполагается, что перед применением процедуры "удалить" всегда
применяется функция узел_есть, чтобы не было попытки удалить несуществующий
узел).
"Запись" - это имя объекта типа "связи" (объекта сеть(в_узле).связан),
локальное для процедуры "переписать". Общий вид объявления процедуры:
<спецификация процедуры> is
<локальные объявления> ;
begin
<операторы> ;
end процедуры ;
Оборот for j in <диапазон> - это ОБЪЯВЛЕНИЕ УПРАВЛЯЮЩЕЙ ПЕРЕМЕННОЙ
ЦИКЛА, область действия которой от объявления до конца цикла. Внутри
БАЗИСНОГО ЦИКЛА (от loop до end loop) j считается постоянной. Если диапазон
пуст (это бывает, когда его правая граница меньше левой), базисный цикл не
выполняется ни разу. Иначе он выполняется при всех последовательных
значениях j из указанного диапазона, если только выполнение всего оператора
цикла не будет досрочно завершено оператором выхода (exit).
В нашем случае все имена узлов из массива "узлы" с индексами от
"после+1" до "число" перемещаются на позиции с предыдущим индексом. В
результате массив "узлы" содержит все старые связи, кроме вычеркнутой, а их
общее количество предварительно скорректировано (уменьшено на 1).
Шаг 3.8. Содержательный эффект процедуры "связать" также очевиден:
она применима к включенным в сеть узлам; после ее применения узлы считаются
связанными.
Снова можно было бы реализовать такой эффект по-разному. Выберем
следующий способ, учитывающий конкретные реализации остальных наших
процедур: в запись о каждом из аргументов процедуры "связать" будем
добавлять указание о связи с другим аргументом.