В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 8
Текст из файла (страница 8)
Скалярные типы. Когда определяют перечисляемый тип, явно указывают
перечень лексем, которые и составляют область возможных значений объектов
вводимого типа. Такой перечень может быть списком дней недели (понедельник,
вторник, среда, четверг, пятница, суббота, воскресенье), списком символов
некоторого алфавита (`A',`B',...,`Z') и т.п. Перечисляемые типы избавляют
программиста от необходимости кодировать содержательные объекты целыми
числами. Перечисляемые типы BOOLEAN (логический) и CHARACTER (символьный)
считаются ПРЕДОПРЕДЕЛЕННЫМИ, т.е. встроенными в язык и действующими без
предварительного явного объявления в программе. Набор символов типа
CHARACTER соответствует алфавиту ASCII - Американскому стандартному коду для
обмена информацией.
Числовые типы обеспечивают точные и приближенные вычисления. В точных
вычислениях пользуются ЦЕЛЫМИ типами. Область возможных значений для таких
типов - конечный диапазон целых чисел. В приближенных вычислениях пользуются
либо АБСОЛЮТНЫМИ типами (задается абсолютная допустимая погрешность), либо
ОТНОСИТЕЛЬНЫМИ типами (задается относительная погрешность). Абсолютная
погрешность задается явно и называется ДЕЛЬТОЙ, относительная погрешность
вычисляется по заданному допустимому количеству значащих цифр в
представлении числа. Подразумевается, что абсолютные типы будут представлены
машинной арифметикой с фиксированной точкой, а относительные - с плавающей.
Числовые типы INTEGER (целый), FLOAT (плавающий) и DURATION (временные
задержки для управления задачами) считаются предопределенными.
Составные типы. Скалярные типы (и перечисляемые, и числовые) выделяются
тем, что объекты этих типов считаются атомарными (не имеющими составляющих).
Составные типы, в отличие от скалярных, позволяют определять
структурированные объекты (массивы и записи). Массивы служат значениями
регулярных типов - компоненты массивов доступны по индексам. "Регулярность"
массивов проявляется в том, что все компоненты должны быть одного типа.
Записи (структуры) служат значениями комбинированных типов - их компоненты
могут быть различных типов; компоненты записей доступны по именам-
селекторам. Имена компонент одной и той же записи должны быть различны;
компоненты называются также ПОЛЯМИ записи.
Строение записей одного типа может зависеть от значений выделенных
полей, называемых ДИСКРИМИНАНТАМИ. Дискриминанты играют роль параметров
комбинированного типа - задавая набор дискриминантов, выбирают определенный
вариант структуры объектов этого типа. Поэтому типы с дискриминантами
называют также ВАРИАНТНЫМИ типами.
Ссылочные типы. Если структура объектов составных типов (в случае
вариантных типов - все варианты такой структуры) фиксируется статически
(т.е. до начала выполнения программы), то ссылочные типы позволяют создавать
и связывать объекты динамически (при исполнении программы, точнее, при
исполнении ГЕНЕРАТОРОВ). Тем самым появляется возможность динамически
создавать сколь угодно сложные конгломераты объектов. Генератор создает
объект указанного (статически известного) типа и обеспечивает доступ к вновь
созданному объекту через переменную соответствующего ссылочного типа.
Передавая (присваивая) ссылки, можно организовать произвольные структуры.
Важно, что и элементы, и связи в таких динамических структурах можно менять
при исполнении программы.
Приватные типы. Доступ к ПРИВАТНЫМ объектам (их называют также
абстрактными объектами, а соответствуюшие типы - абстрактными типами данных
(АТД) находится под полным контролем автора приватного типа. Такой тип
всегда определяется в пакете который называется ОПРЕДЕЛЯЮЩИМ пакетом для
этого типа. Спецификация определяющего пакета фиксирует полный набор
операций и тех ролей в этих операциях, в которых могут фигурировать объекты
нового типа. В определяющем пакете фиксируется и реализация приватного типа,
однако в использующих этот пакет модулях она непосредственно недоступна -
только через явно перечисленные автором пакета допустимые операции. Поэтому
реализацию можно изменять, не заставляя переделывать использующие модули.
Концепция типа в Аде дополнена аппаратом ПОДТИПОВ, (они ограничивают
область значений, не затрагивая допустимых операций), а также аппаратом
ПРОИЗВОДНЫХ типов (они образуются из уже известных типов, наследуя связанные
с ними значения и операции).
Поговорим об остальных средствах языка. Посредством УКАЗАТЕЛЯ
ПРЕДСТАВЛЕНИЯ можно уточнить требования к реализации определенных типов на
целевой машине. Например, можно указать, что объекты такого-то типа следует
представить заданным количеством битов, что такие-то поля записи должны
располагаться с такого-то адреса. Можно указать и другие детали реализации,
вплоть до прямой вставки машинных команд. Ясно, что, с одной стороны,
подробное описание представления мешает переносу программы в другую
операционную обстановку. С другой стороны, оно может оказаться исключительно
важным для качества и даже работоспособности программы. Явное указание
представления помогает отделять машинно-независимые части модулей от
машинно-зависимых. В идеале только указатели представления и потребуется
менять при переносе программы.
Обмен с внешней средой (ввод-вывод) обслуживается в Аде
предопределенными библиотечными пакетами. Имеются средства обмена не только
значениями предопределенных типов (как в Паскале), но и типов, определяемых
программистом.
Наконец, имеются средства статической параметризации модулей
(действующие до начала исполнения программы, в период компиляции) - аппарат
РОДОВЫХ модулей. Параметры таких модулей (родовые параметры) - в отличие от
динамических параметров подпрограмм и процедур - могут быть не только
объектами данных некоторого типа, но и такими объектами, как типы и
подпрограммы (которые в Аде не считаются объектами данных). Так что общие
модули, рассчитанные на применение ко всем типам данных определенной
категории, в Аде следует оформлять как родовые.
На этом закончим краткий обзор языка.
2.3. Пошаговая детализация средствами Ады
Рассмотрим следующую задачу.
Содержательная постановка. Необходимо предоставить пользователю
комплекс услуг, позволяющих ему моделировать сеть связи. Пользователь должен
иметь возможность изменять сеть (добавлять и убирать узлы и линии связи), а
также получать информацию о текущем состоянии сети.
Требования к реализации. Во-первых, должна быть гарантирована
целостность сети при любых возможных действиях пользователя. Другими
словами, ни при каких условиях не должны возникать линии связи, не ведущие
ни в один узел. Во-вторых, предполагается развивать возможности
моделирования (например, отражать стоимость связей). Важно, чтобы при этом у
пользователя не возникала необходимость изменять готовые программы. Оба эти
требования можно удовлетворить, если строго регламентировать доступ
пользователя к представлению сети в памяти компьютера. Тогда заботу о
целостности сети можно возложить на средства доступа к ней, а при развитии
комплекса услуг можно изменять представление сети, сохраняя все старые
средства доступа (и следовательно, ранее работавшие программы пользователя).
Первый шаг детализации. Уточнение постановки задачи в терминах языка
Ада
Так как речь идет не об алгоритме, а о предоставлении пользователю
комплекса услуг, в Ада-терминах естественно отобразить этот комплекс на
совокупность "логически связанных" объектов, в данном случае - связанных по
меньшей мере совместным использованием. Другими словами, первое наше решение
состоит в том, чтобы создавать ПАКЕТ, а не подпрограмму или задачу.
Вспоминая, что разделение спецификации и тела пакета позволит скрыть от
пользователей пакета детали реализации (в частности, представление сети, в
полном соответствии с требованиями), получаем еще одно подтверждение, что
решение правильное.
Итак, создаем ПАКЕТ. Нужно придумать ему название, выражающее
назначение предоставляемого комплекса услуг. Попробуем "сеть". Нехорошо. По-
видимому, так лучше называть тот объект, который будет моделироваться, и чье
представление нужно скрыть в теле нашего пакета. Попробуем "моделирование
сети связи". Лучше, но слишком конкретно. Хотя в постановке задачи и
требованиях речь идет именно о моделировании сети связи, однако специфика
связи (кроме самой сети) ни в чем не отражена (нет и речи о пропускной
способности каналов, классификации сообщений и т.п.), да и специфика
моделирования не затронута (никаких моделей отправителей, получателей и
т.п.). Скорее мы собираемся предоставить лишь комплекс услуг по управлению
сетью. Так и назовем пакет: "управление_сетью".
[Точное название настраивает на то, чтобы в пакете не было лишнего, а
пользователю помогает применять наш комплекс и в других областях, не
обязательно для моделирования сетей связи].
Второй шаг детализации. Теперь нужно написать спецификацию пакета,
объявив все объекты, с которыми сможет работать пользователь.
0. with параметры_сети; use параметры_сети;
1. package управление_сетью is
2. type узел is new INTEGER range 1 .. макс_узлов;
3. type число_связей is new INTEGER range 0..макс_связей ;
4. type индекс_узла is new INTEGER range 1..макс_связей;
5. type перечень_связей is array (индекс_узла) of узел;
6. type связи is
7. record
8. число : число_связей := 0;
9. узлы : перечень_связей;
10. end record ;
11. -- операции над сетью
12. procedure вставить (X : in узел);
13. procedure удалить (X : in узел);
14. procedure связать (X, Y : in узел);
15. -- сведения о текущем состоянии сети
16. function узел_есть (X : узел) return boolean;
17. function все_связи (X : узел) return связи;
18. end управление_сетью;
Текст спецификации пакета с названием "управление_сетью" при
первоначальном знакомстве может показаться непонятным. Во всяком случае не
верится, что он получен одним шагом детализации. Действительно, шаг зависит
как от привычки проектировщика, так и от свойств языка. Ведь в общем случае
далеко не каждый мелкий шаг поддерживается подходящим законченным языковым
конструктом. Например, в Аде шаги нельзя дробить сколь угодно мелко хотя бы
потому, что действует правило последовательного определения : при очередном
определении можно использовать только предопределенные, внешние или ранее
объявленные имена.
Но при пошаговой детализации нельзя заранее объявить те имена, которые
понадобятся - они попросту неизвестны. Когда проектируют совокупность
модулей, это не помеха (порядок модулей не существенен). А вот внутри
модулей правило последовательного определения мешает пошаговой детализации
[особенно внутри пакетов; почему?]. Приходится либо применять средства,
выходящие за рамки Ады (например, псевдокод), либо записывать пакет "с конца
к началу" - этот порядок с учетом правила последовательного определения
лучше отражает последовательность появления имен при пошаговой детализации.
[С точки зрения принципа технологичности любые несоответствия языка
потребностям пошаговой детализации служат источником "точек роста", намечают
направление развития либо самого языка, либо других связанных с ним
технологических инструментов. Для Ады, в частности, разрабатываются
специальные средства поддержки пошагового конструирования программ].
Упражнение. Укажите внешний эффект (исходные данные и результаты) хотя
бы одного из таких средств.
Подсказка. Редактор, располагающий фрагменты Ада-программы в порядке,
соответствующем правилу последовательного определения.
Упражнение (повышенной сложности). Разработайте проект хотя бы одного
такого средства; проект комплекса таких средств.
Итак, проявим более мелкие шаги проектирования нашего пакета.
Шаг 2.1 (строка 17). Объявляем функцию с названием "все_связи",
формальным параметром с названием "X" (значениям этого параметра приписан
тип с названием "узел") и результатом, которому приписан тип с названием
"связи".
[Ниже будем писать короче: функцию "все_связи" с параметром "X" типа
"узел" и результатом типа "связи"].
Эта функция дает возможность узнавать о текущем состоянии сети (точнее,
о связях одного узла). Обратите внимание, пока совершенно неясно, что такое
"узел" и "связи". Это лишь названия, отражающие роль в создаваемом комплексе
услуг тех объектов, которые еще предстоит воплотить в программе.
Шаг 2.2 (строка 16). Нехорошо запрашивать связи узла, не зная, имеется
ли он в сети. Поэтому (продолжая предоставлять средства узнавать о состоянии
сети) объявляем функцию узел_есть с параметром "X" типа узел и результатом
логического типа (BOOLEAN).
Замечание. Обратите внимание, мы записываем только формальные
СПЕЦИФИКАЦИИ (заголовки) функций. Содержащихся в них сведений достаточно,
чтобы можно было (столь же формально) написать вызов такой функции. Но, во-
первых, рано или поздно придется написать ТЕЛО функции (сделаем это в ТЕЛЕ