В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 19
Текст из файла (страница 19)
очередной пример разумной абстракции. Абстракция от сути характеристик
привела к концепции уникальности типа. Концепция уникальности - к простоте
прогнозирования-контроля. А простота прогнозирования-контроля позволяет по-
новому взглянуть на саму концепцию типа.
Действительно, в ранних ЯП под характеристиками данных обычно
понимались характеристики их значений (уже упоминавшиеся разрядность,
точность, вид выделяемой памяти и т.п.). Конечно, неявно всегда
подразумевалось, что к значениям с одними характеристиками применимы одни
операции, а к значениям с другими характеристиками - другие. И раньше
чувствовалось, что класс применимых операций - существенная содержательная
характеристика класса данных (ведь это естественно следует из понятия
данного как разумной абстракции от конкретной операции).
Но идею явного связывания класса данных с классом применимых операций
трудно воплотить в рамках структурной эквивалентности типов. Ведь для
определения класса данных, к которым применима операция, придется (как,
например, в Алголе-68) производить нетривиальные расчеты совокупности
данных, структурно эквивалентных параметрам операции.
Именная эквивалентность в концепции уникальности упростила связывание с
типом любых характеристик. Легко узнать и классы данных, связанных с любой
операцией - имена типов явно указаны в спецификациях ее параметров.
Может показаться, что нетрудно проделать и обратное - указать для
каждого типа все применимые операции. Но представим себе, что пакет
управление_сетями предоставлен в распоряжение большого коллектива
пользователей. Каждый из них волен написать сегменты, использующие этот
пакет, и в этих сегментах ввести свои операции над объектами типа "сети".
Например, один ввел процедуру выделения связных подсетей, другой - процедуру
подсчета хроматического числа сети, третий - вычисления минимального
маршрута между двумя узлами. Следует ли считать характеристикой типа "сети"
набор из всех этих операций? И каков содержательный смысл в такой
характеристике? Ведь в разных контекстах доступны разные элементы этого
набора.
Лучше считать характеристикой типа относительно постоянное его
свойство, связанное с ним в любых контекстах. Таковы КЛАСС ЗНАЧЕНИЙ объектов
этого типа и БАЗОВЫЙ НАБОР ОПЕРАЦИЙ, применимых к этим объектам. В Аде эти
две характеристики связываются с типом соответственно ОБЪЯВЛЕНИЕМ ТИПА и
ОПРЕДЕЛЯЮЩИМ ПАКЕТОМ.
4.5. Воплощение концепции уникальности типа. Определение и
использование типа в Аде (начало)
Концепция типа воплощена в Аде, в основном, четырьмя конструктами:
объявлением типа, пакетом, объявлением подтипа и объявлением объекта. В
совокупности они и позволяют считать, что тип в Аде обладает, кроме имени,
еще двумя важнейшими характеристиками - классом значений и набором
применимых операций. С каждым из названных четырех конструктов мы уже
встречались. Поговорим подробнее об их строении, смысле и взаимодействии.
4.5.1. Объявление типа. Конструктор типа. Определяющий пакет
Объявление типа вводит имя нового типа и связывает это имя с
конструктором типа. Последний служит для создания нового типа из уже
известных. Конструктор типа располагается в объявлениях типов после
ключевого слова is.
Создать (объявить) новый тип в Аде - это значит определить класс
допустимых значений объектов этого типа и набор базовых операций, связанных
с создаваемым типом.
В Аде предопределены некоторые типы данных (обслуживающие наиболее
общие потребности проблемной области - универсальный_целый,
универсальный_вещественный и другие), а также класс допустимых значений
перечисляемых типов. Кроме того, предопределены операции, применимые к
объектам целых категорий типов. Например, со всеми регулярными типами
связана операция получения указателя на компоненту массива (индексация), со
всеми комбинированными типами связана операция получения указателя на
компоненту записи - выборка, со всеми так называемыми ДИСКРЕТНЫМИ типами -
получение по заданному значению последующего или предыдущего. Если явно не
оговорено обратное, то к объектам любого типа можно применять сравнение на
равенство и неравенство, извлечение и присваивание значения.
Предопределенные типы, классы значений и операции служат исходным
материалом для нескольких категорий конструкторов типа - у каждой категории
типов свой конструктор.
Объявление типа (а, следовательно, и конструктор типа) служит для того,
чтобы полностью и окончательно определить класс допустимых значений и в
общем случае лишь частично определить базовые операции.
Полный и окончательный набор базовых операций некоторого типа
фиксируется его определяющим пакетом (так называется пакет, спецификация
которого содержит объявление этого типа). В спецификации определяющего
пакета вместе с объявлением нового типа могут присутствовать и объявления
новых операций, связанных с ним.
Например, конструктор производного типа, создавая тип "узел",
определяет для него класс (в данном случае - диапазон) значений и связывает
с типом узел обычные операции над целыми числами (наследуемые у
родительского типа INTEGER). Остальные базовые операции для объектов типа
узел (вставить, удалить, связать и др.) определены в конце спецификации
пакета управление_сетью, который и служит для этого типа определяющим
пакетом. Подчеркнем, что для типов, объявляемых в теле пакета, он,
естественно, определяющим не считается (почему?).
Еще пример. Конструктор КОМБИНИРОВАННОГО типа, создавая тип "связи",
определяет для него, во-первых, класс значений (записи с двумя полями -
первое с СЕЛЕКТОРОМ "число" типа число_связей, второе с селектором "узлы"
типа "перечень-связей"). Во-вторых, этот конструктор определяет обычные для
всех комбинированных типов операции доступа как ко всему значению объекта
(по имени объекта), так и к его компонентам по составным именам с
использованием селекторов. Еще одна (и последняя) базовая операция для этого
типа определена в пакете управление_сетью - это операция все_связи. Доступ к
полному значению объекта типа "связи" использован в реализации функции
все_связи (в операторе возврата), а доступ к одному полю по селектору - в
реализации операций вставить, удалить, чистить, переписать.
Конструктор ПРИВАТНОГО типа, создавая тип "сети", определяет в качестве
базовых операций только присваивание и сравнение на равенство и неравенство.
Определяющий пакет управление_сетями добавляет базовые операции вставить,
удалить и др.
[Обратите внимание: одни и те же операции могут быть базовыми для
различных типов! За счет чего?].
Класс значений для типа "сети" определен полным объявлением в
приватной части, однако пользователю этот класс остается "неизвестным"
(непосредственно недоступным).
Запас предопределенных типов, значений и операций - это базис ЯП, а
конструкторы типа - характерный пример средств развития. Процесс развития ЯП
(с помощью любых конструкторов) начинается с применения конструкторов к
базису. На очередном шаге развития конструкторы применяются к любым уже
определенным сущностям (в том числе и к базисным).
4.6. Конкретные категории типов
4.6.1. Перечисляемые типы. "Морская задача"
Займемся технологической потребностью, которая часто встречается в так
называемых дискретных задачах (в дискретной математике вообще). Речь идет о
потребности работать с относительно небольшими конечными множествами.
Замечание (о конечных множествах). Когда конечные множества очень
большие, то с точки зрения программирования они могут оказаться неотличимыми
от множеств бесконечных. Специфика конечного сказывается тогда, когда
мощность множества удается явно учесть в программе. При этом, в соответствии
с принципом согласования абстракций, средства объявления конечных множеств
должны быть согласованы со средствами манипулирования как множествами в
целом, так и отдельными их элементами.
Рассмотрим очень упрощенную задачу управления маневрами корабля.
Содержательная постановка. Корабль движется по определенному курсу.
Поступает приказ изменить курс. Требуется рассчитать команду, которую
следует отдать рулевому, чтобы направить корабль по новому курсу (совершить
нужный маневр).
Модель задачи. Будем считать, что возможных курсов только четыре:
север, восток, юг и запад. Можно отдать лишь одну из четырех команд: прямо,
налево, направо и назад. Исполнение команд "налево" и "направо" изменяет
курс на 90 градусов. Требуется рассчитать новую команду по заданным старому
и новому курсам. Например, для старого курса "восток" и нового курса "север"
нужный маневр выполняется по команде "налево".
Программирование (полная формализация задачи). Попытаемся вновь
применить пошаговую детализацию.
Шаг 1. Нужно "рассчитать" команду. Это уже не комплекс услуг, а одна
операция. Представим ее функцией, для которой старый и новый курсы служат
аргументами, а команда рулевому - результатом. Чтобы сразу написать
спецификацию такой функции на Аде, нужно верить, что на последующих шагах
можно будет ввести подходящие типы аргументов и результата. Поверим, не
думая пока об особенностях этих типов. Тогда нужную операционную абстракцию
можно воплотить следующей спецификацией:
function маневр (старый, новый : курс) return команда ;
Шаг 2. Как формализовать понятие "команда"?
С точки зрения ЯП это должен быть тип данных - ведь имя "команда"
использовано в спецификации параметров функции. Следовательно, нужно
определить связанные с этим типом значения и базовые операции.
С содержательной точки зрения ясно, что нужны только названия команд.
Что с ними можно делать - неясно, это выходит за рамки модели нашей задачи
(наша модель неполна в том отношении, что в ней отсутствует, например,
модель устройства корабля и модель рулевого как части корабля). Поэтому,
формализуя понятие "команда", удовлетворимся тем, что проявим список
существенных команд и отразим в названиях команд их роль в управлении
кораблем.
Существенных команд всего четыре: прямо, налево, направо, назад. Таким
образом, нужно объявить тип данных с четырьмя перечисленными значениями. Ада
позволяет это сделать с помощью следующего объявления ПЕРЕЧИСЛЯЕМОГО типа:
type команда is (прямо, налево, направо, назад) ;
Не зря эта категория типов названа "перечисляемыми типами" - все
допустимые значения явно перечислены.
[Такие типы называют иногда "перечислимыми". Однако этот термин в
математике занят, относится тоже к множествам и имеет другой смысл
(перечислимые множества вполне могут быть бесконечными). К тому же специфика
рассматриваемых типов в том и состоит, что их значения явно перечисляют при
определении такого типа.]
Перечисляемые типы придуманы Н.Виртом и впервые появились в созданном
им языке Паскаль. В наших примерах до сих пор были такие типы, для которых
имело смысл говорить об описании множества значений, о создании нового или
разрушении старого значения. Но никогда не шла речь о явном перечислении в
программе всех значений некоторого типа. Другими словами, определения типов
были всегда интенсиональными и никогда - экстенсиональными. Это было и не
нужно и, как правило, невозможно - шла ли речь о целых или вещественных
числах, о массивах или записях. "Не нужно" означает, что такова была
дисциплина применения этих типов. "Невозможно" связано с их практической
бесконечностью.
В нашем случае и можно, и нужно давать экстенсиональное определение
типа, явно перечислив все значения типа "команда". Можно, потому что их
всего четыре, и мы их уже перечислили. А зачем это нужно?
Нужно потому, что всякое интенсиональное определение опирается на
существенные индивидуальные свойства элементов определяемого множества. А в
нашем случае таких свойств нет! Годятся любые различные имена для команд
(желательно мнемоничные, отражающие содержательную роль команд в нашей
задаче). Захотим, будет {прямо, налево, направо, назад}, не понравится -
сделаем {вперед, влево, вправо, обратно} или {так_держать, лево_руля,
право_руля, задний_ход} и т.п. Поэтому нет иного способа догадаться о
принадлежности конкретного имени к типу "команда", кроме как увидеть его в
соответствующем списке.
Итак, объявление перечисляемого типа вводит мнемонические имена для
компонент модели решаемой задачи (в нашем случае - имена команд). До