Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки программирования. Концепции и принципы
.pdf
Данные и типы
121
Вспомним классификацию данных и в ней – фактор изменчивости. Он играет
особую роль, так как касается самого динамичного атрибута объекта – его зна
чения.
Поэтому с фактором изменчивости в наибольшей степени связано противоре
чие между потребностью гибко изменять поведение объектов и потребностью
прогнозировать это поведение. Другими словами, это противоречие между по
требностью в мощных операционных абстракциях, применимых к весьма разно
образным данным, и потребностью ограничить их применение, чтобы достичь на
дежности и эффективности.
Первая потребность требует свободы, вторая – дисциплины. Чем точнее удает
ся прогнозировать изменчивость, чем в более жесткие рамки «зажимается» воз
можное поведение, тем надежнее контроль, больше возможностей экономить
ресурсы. Но вместе с этим снижается общность, растут затраты на аппарат про
гнозированияконтроля, растут затраты на создание близких по назначению про
грамм, на их освоение.
Мы уже сталкивались с этим противоречием, когда занимались проблемой по
лиморфизма. Но тогда речь шла о небольшом наборе разновидностей (типов)
объектов, и противоречие удалось разрешить за счет конечного набора операций
с одним и тем же именем (каждая для своего набора типов объектов). Теперь нас
интересует такая ситуация, когда разновидностей неограниченно много. Именно
такая ситуация создалась при попытке абстрагироваться от длины вектора в про
грамме «минус».
Абстракция от длины вектора (продолжение). В чем конкретно противоречие?
С одной стороны, нужна как можно более мощная операционная абстракция,
применимая к векторам любой длины. Для этого необходимо иметь возможность
ввести согласованный с такой абстракцией обобщенный тип, значениями которо
го могут быть массивы произвольной длины. Иначе возникнет противоречие
с концепцией уникальности типа (какое?).
Такой тип в Аде объявить можно. Например, так:
type вектор_любой_длины is array(INTEGER range < >) of
INTEGER;
Вместо конкретного диапазона индексов применен оборот вида
òèï range < >
который и указывает на то, что объявлен так называемый НЕОГРАНИЧЕННЫЙ
регулярный тип, значениями которого могут быть массивы с любыми диапазона
ми индексов указанного типа (в нашем случае – целого).
Аналогичный неограниченный регулярный тип с диапазонами индексов пере
числяемого типа вводит объявление
type таблица is array(буква range < >) of INTEGER;
Значения такого типа могут служить, например, для перекодировки букв в це
лые числа.
Упражнение. Напишите соответствующую программу перекодировки.

122
Вернемся к типу вектор_любой_длины. Как объявлять конкретные объекты
такого типа? Ведь объявление вида
Y : вектор_любой_длины;
не прогнозирует очень важной характеристики объекта Y – его возможной длины
(другими словами, прогнозу не хватает точности с точки зрения распределения
ресурсов программы).
Поэтому само по себе такое объявление не позволяет ни обеспечить эффектив
ность (нельзя распределить память при трансляции), ни настроить функцию на
конкретный аргумент такого типа. И конечно, раз длина вектора не объявлена, то
нет оснований контролировать ее (например, в процессе присваивания).
Современное состояние языков программирования
4.6.5. Подтипы
Чтобы разрешить указанное противоречие, авторы Ады были вынуждены ввести
концепцию ПОДТИПА (специально для квазистатического прогнозирования
контроля изменчивости объектов). Основная идея – в том, чтобы при необходи
мости можно было, с одной стороны, удалить некоторые атрибуты объектов из
сферы статического прогнозированияконтроля, не указывая их при объявлении
типа, а с другой – оставить эти атрибуты для динамического прогнозирования
контроля с помощью подтипов.
Подтип представляет собой сочетание ТИПА и ОГРАНИЧЕНИЯ на допусти
мые значения этого типа. Значения, принадлежащие подтипу, должны, вопер
вых, принадлежать классу значений ограничиваемого типа и, вовторых, удовлет
ворять соответствующему ОГРАНИЧЕНИЮ.
Подтип можно указывать при объявлении объектов. Например,
А : вектор_любой_длины(1..10);
объявляет 10элементный вектор А (причем использовано так называемое ОГРА
НИЧЕНИЕ ИНДЕКСОВ);
выходной : день_недели range сб..вс;
объявляет объект типа день_недели, принимающий значение либо «сб», либо
«вс» (причем применяется так называемое ОГРАНИЧЕНИЕ ДИАПАЗОНА).
Бывают и другие виды ограничений (для вещественных и вариантных комби
нированных типов).
Раньше мы говорили, что объявление объекта связывает с ним некоторый тип.
На самом деле правильнее сказать, что оно связывает с ним некоторый подтип.
Когда ограничение отсутствует, то все значения типа считаются удовлетворяю
щими подтипу.
Подтип, указанный в объявлении объекта, характеризует его во всей области
действия объекта, в течение всего периода его существования. Поэтому становится
возможным, вопервых, учитывать подтип при распределении памяти для объекта
(например, для массива А выделить ровно десять квантов памяти); вовторых, кон
тролировать принадлежность подтипу при присваивании. Последнее приходится

Данные и типы
123
иногда делать динамически (поэтому и идет речь о «квазистатическом контроле»).
Это может замедлять работу программы, зато повышает надежность.
Пусть, например, объявлены объекты
А,В : вектор_любой_длины(1..10);
выходной : день_недели range сб..вс;
праздник : день_недели;
день_рождения : день_недели;
C,D : вектор_любой_длины(1..11);
будний_день : день_недели range пн..пт;
учебный_день : день_недели range пн..сб;
Тогда присваивания
А := В; В := А; праздник := день_рождения;
день_рождения := будний_день;
праздник := выходной;
С := D; D := С;
не потребуют никакой дополнительной динамической проверки, так как допусти
мые значения выражений в правой части присваивания всегда удовлетворяют
подтипам объектов из левой части.
Присваивания
А := С; С := А; А := D; В := D; D := A; D := В;
будний_день := выходной;
выходной := будний_день;
также не требуют дополнительной динамической проверки – они всегда недопус
тимы, и обнаружить это можно статически, при трансляции.
А вот присваивания
будний_день := учебный_день; будний_день := праздник;
учебный_день := выходной; учебный_день := праздник;
нуждаются в динамической проверке (почему?).
4.6.6. Принцип целостности объектов
Вернемся к нашей процедуре «минус», вооруженные концепцией подтипа. Допу
стим, что ее параметр станет типа вектор_любой_длины. Как обеспечить настрой
ку на конкретный вектораргумент? Другими словами, абстракцию мы обеспечи
ли (есть обобщенный тип), а вот реальна ли она (чем обеспечена конкретизация)?
Вспомним, как это делается в Алголе60 или Фортране. Границы конкретного
массивааргумента нужно передавать обрабатывающей процедуре в качестве до
полнительных аргументов. Это и неудобно, и ненадежно (где гарантия, что будут
переданы числа, совпадающие именно с границами нужного массива?).
Другими словами, перед нами – пример нарушения целостности объекта. Со
стоит оно в том, что цельный объектмассив при подготовке к передаче в качестве
параметра приходится разбивать на части (имя – отдельно, границы – отдельно),
а в теле процедуры эти части «собирать» (к тому же при полном отсутствии конт

124
Современное состояние языков программирования
роля – ведь транслятор лишен информации о связи между границами и именем
массива; знает о ней лишь программист).
Создатели более современных ЯП руководствуются принципом (сохранения)
целостности объектов. Суть его в том, что ЯП должен обеспечивать возможность
работать с любым объектом как с единым целым (не требуя дублировать характе
ристики объекта и тем самым устраняя источник ошибок). Более точно этот прин
цип можно сформулировать так:
вся необходимая информация об объекте должна быть доступна через его имя.
Соблюдение принципа целостности требует включения в базис соответствую
щих средств доступа к характеристикам объектов. В Аде, где этот принцип поло
жен в основу языка, предопределены универсальные функции, позволяющие уз
навать атрибуты конкретных объектов по именам этих объектов.
Они так и называются – АТРИБУТНЫЕ ФУНКЦИИ. Тот или иной набор
атрибутных функций связывается с объектом в зависимости от его типа. В частно
сти, для объектов регулярного типа определены атрибутные функции нигр(k) и
вегр(k), сообщающие нижнюю и верхнюю границы диапазона индексов по kму
измерению. Например:
À'íèãð(1) = 1, Â'íèãð(1) = 1,
À'âåãð(1) = 10, Ñ'íèãð(1)= 1,
D'âåãð(1) = 11.
Абстракция от длины вектора (окончание). Теперь совершенно ясно, как объявить
процедуру «минус», применимую к любому массиву типа вектор_любой_длины.
procedure минус(X : in out вектор_любой_длины) is
begin
for j in(Õ'íèãð(1)..Õ'âåãð(1)) loop
X(j) :=X(j);
end loop;
end минус;
Для одномерных массивов вместо нигр(k) и вегр(k) можно писать короче –
нигр и вегр, так что заголовок цикла может выглядеть красивее:
for j in(Õ’íèãð..Õ’âåãð) loop .
Итак, мы полностью справились с нашей второй абстракцией. При этом вос
пользовались принципом целостности, чтобы обеспечить реальность абстракции.
Со стороны данных для этого понадобилось ввести неограниченные типы и огра
ниченияподтипы, а со стороны операций – атрибутные функции (опять потребо
валось согласовывать абстракции!).
Сочетание относительно «свободных» типов с постепенным ограничением измен
чивости вплоть до полной фиксации значений объектов (когда они становятся
константами) широко применяется при прогнозированииконтроле поведения
объектов в Аде. Подтип играет роль, аналогичную той роли, которую играл сам тип
в ранних ЯП, – ведь подтип затрагивает только совокупность значений объектов,
не касаясь применимых к ним операций (кроме присваивания, как было показано
совсем недавно).

Данные и типы
125
4.6.7. Объявление подтипа
Подтип, вводимый при объявлении объекта, является анонимным. Но можно
объявлять именованные подтипы. Естественно делать это тогда, когда одни и те
же ограничения нужно использовать для объявления многих различных объек
тов. Вот несколько примеров именованных подтипов:
subtype рабочий_день is день_недели range пн..пт;
subtype натуральный is INTEGER range 0.. INTEGER 'последний;
subtype положительный is INTEGER range 1..INTEGER'последний;
subtype цифра is CHARACTER range '0'..'9';
В качестве простого упражнения объявите подтип весенний_месяц, выход
ной_день, восьмеричная_цифра и т. п.
По внешнему виду объявление подтипа похоже на объявление производного
типа. Однако это конструкты совершенно разного назначения. Разберемся с этим
подробнее.
4.6.8. Подтипы и производные типы.
Преобразования типа
Формально отличие производных типов от подтипов должно быть уже известно.
Производный тип – это именно новый тип, характеризуемый классом значений и
набором базовых операций.
По сравнению с родительским типом класс значений производного типа мо
жет быть сужен (за счет ограничения при объявлении производного типа), а на
бор базовых операций расширен (за счет объявлений базовых операций в опреде
ляющем производный тип пакете).
А для подтипа по сравнению с базовым типом набор базовых операций может
быть только сужен (за счет ограничения допустимых значений).
Кроме того, производный тип в общем случае несовместим по присваиванию и
передаче параметров как с родительским типом, так и с другими производными
типами одного родительского типа. Например:
type год is new INTEGER range 0..2099;
type этаж is new INTEGER range 1..100;
А: год; В: этаж;
…
A := В; – недопустимо!
(Несовместимость типов, хотя значения заведомо попадут в нужный диапазон.)
Имя подтипа служит сокращением для сочетания ограничиваемого типа (на
зовем его БАЗОВЫМ ТИПОМ) и ограничения. Когда такое имя используется
при объявлении объекта, считается, что объявлен соответственно ограниченный
объект базового типа. Когда такое имя применяется в спецификации параметра
процедуры или функции, то аргументом может быть любой объект базового типа,
удовлетворяющий соответствующему ограничению. Объектам различных подти

126
Современное состояние языков программирования
пов одного и того же базового типа можно присвоить любое значение базового
типа, лишь бы оно было из подкласса, выделяемого подтипом левой части.
Содержательные роли, которые призваны играть в программе объекты различ
ных подтипов одного базового типа (при квалифицированном использовании
ЯП), должны быть аналогичными, взаимозаменяемыми (с точностью до ограни
чений на значения).
Другое дело – содержательные роли производных типов одного итого же роди
тельского типа. Производные типы вводятся именно для того, чтобы можно было
контролировать, применяются ли они точно по назначению. Поэтому объекты
различных производных типов в общем случае не считаются взаимозаменяемыми
и по присваиванию (а также сравнениям) несовместимы (компилятор обязан это
контролировать). Содержательная роль различных типов, производных от одного
и того же родительского типа, выражается в том, что им соответствуют совершен
но различные наборы операций.
Вместе с тем при необходимости между такими (родственными) типами допу
стимы явные преобразования типа.
Лес типов. Назовем лесом типов ориентированный граф, вершинами которого
служат типы, а дуги соединяют родительский тип с производным (рис. 4.1).
INTEGER
год этаж
нижний_этаж
type летний_месяц is new месяц range июнь..август;
type весенний_месяц is new месяц range март..май
летний_месяц весенний_месяц
Рис. 4.1
где type нижний_этаж is new этаж range 1..3;
Месяц
Как видите, лес типов в программе может состоять из отдельных деревьев.
Родственные типы и преобразования между ними. Типы из одного дерева в ле
су типов называются РОДСТВЕННЫМИ. В Аде допустимы явные преобразова
ния между родственными типами, которые указываются с помощью имени так
называемого целевого типа, то есть типа, к которому следует преобразовать дан
ное. Каждое определение производного типа автоматически (по умолчанию) вво
дит и операции преобразования родственных типов (но применять эти операции
нужно явно!). Например, можно написать
À := ãîä(Â);
а также
 := ýòàæ(À);

Данные и типы
Обе эти операции (и «год», и «этаж») введены по умолчанию указанными
выше объявлениями производных типов, но применены явно, когда потребова
лось присвоить объект одного родственного типа другому. Во втором присваива
нии потребуется проверить попадание в диапазон допустимых значений.
Преобразования родственных типов (с учетом различных содержательных ро
лей объектов разных типов) далеко не всегда оправданы. Именно поэтому и вво
дятся явные преобразования, которые программист должен указать сознательно.
Так надежность согласуется с гибкостью. По существу, в таких случаях програм
мист явно говорит, что полученное значение должно в дальнейшем играть дру
гую содержательную роль.
Например, в задаче моделирования учреждения может появиться тип «сотруд
ник», характеризуемый целой анкетой атрибутов и набором базовых операций
(«загружен_ли», «выполнить_задание», «включить_в_группу» и т. п.). Есть и
type рук_группы is new сотрудник;
со своими базовыми операциями («дать_задание», «подготовить_план_работы»,
«где_сотрудник» и т. п.).
Пусть объявлены объекты
А: сотрудник;
В: рук_группы;
Тогда присваивание
В := рук_группы(А);
содержательно может означать «повышение» сотрудника А. Ясно, что «автомати
чески» такое преобразование не делается!
127
4.6.9. Ссылочные типы
(динамические объекты)
До сих пор в наших примерах встречались лишь статические и квазистатические
объекты. Время существования статических объектов совпадает с полным време
нем выполнения всей программы. Время существования квазистатических объек
тов согласуется со временем очередного исполнения их статически определимой
области действия.
Область действия объекта – это часть текста программы, при выполнении ко
торой объект считается доступным (например, для чтения или записи его значе
ния, для передачи объекта в качестве параметра и т. п.).
Например, в Паскале область действия (локальной) переменной – процедура
или функция, где эта переменная объявлена. В Аде (квази)статические объекты
создаются при исполнении объявлений и исчезают в момент завершения испол
нения соответствующей области действия.
Так как квазистатические объекты создаются и исчезают синхронно с испол
нением фиксированных компонент текста программы (областей действия этих
объектов), то в каждой из них легко явно назвать все квазистатические объекты

128
индивидуальными именами, фигурирующими непосредственно в тексте про
граммы (то есть также квазистатическими). Это еще один признак квазистати
ческого объекта – такие и только такие объекты могут иметь квазистатические
имена.
Но имена содержательных обрабатываемых объектов далеко не всегда удобно
фиксировать в тексте программы. Ведь эти объекты могут находиться во внешней
для программы среде или возникать динамически как во внешней среде, так и
в самой программе при обработке других объектов. Такие динамически возни
кающие объекты называют динамическими объектами. Иногда динамическими
объектами называют и (квази)статические объекты с динамически изменяющи
мися размерами или структурой.
Обычный прием, обеспечивающий доступ к динамическим объектам, состоит
в том, что ссылки на такие объекты служат значениями квазистатических объек
тов, явно фигурирующих в программе. Когда ЯП не предоставляет адекватных
выразительных средств, динамические объекты приходится моделировать с по
мощью квазистатических объектов или их частей. Например, на Фортране дина
мические очереди, списки, таблицы, стеки приходится моделировать (квази)ста
тическими массивами.
Таким образом, возникает технологическая потребность в категории так назы
ваемых ССЫЛОЧНЫХ ТИПОВ, то есть типов данных, класс значений кото
рых – ссылки на динамические объекты.
Динамические объекты отличаются от статических или квазистатических,
вопервых, тем, что создаются при выполнении так называемых ГЕНЕРАТОРОВ,
а не при обработке объявлений; вовторых, тем, что доступ к ним осуществляется
через объекты ссылочных типов.
Поэтому и время существования динамических объектов в общем случае свя
зано не с местом их первого упоминания в программе, а со временем существова
ния ссылки. Ссылку можно передавать от одного ссылочного объекта другому,
сохраняя динамический объект даже при исчезновении ранее ссылавшихся на
него квазистатических объектов.
Чтобы сохранить возможность квазистатического прогнозированияконтроля
поведения динамических объектов, необходимо классифицировать ссылочные
объекты в соответствии с типом динамических объектов, на которые им разреше
но ссылаться. Однако концепция типа не обязана различать динамические и ква
зистатические объекты, так как с точки зрения класса значений и применимых
операций динамический объект не отличается от квазистатического (кроме опе
рации создания и, возможно, уничтожения).
Так и сделано в Аде (как и в Паскале), где объекты одного и того же типа могут
быть как статическими, так и динамическими в зависимости от того, объявлены
они или созданы генератором. Вместе с тем с каждой ссылкой жестко связан тип,
на объекты которого ей разрешено ссылаться.
Современное состояние языков программирования

Данные и типы
129
4.7. Типы как объекты высшего
порядка. Атрибутные функции
Когда тип данных легко обозначать и определять, естественно появляется потреб
ность обращаться с ним самим как с данным, употреблять его в качестве операнда
различных операций без какихлибо априорных ограничений.
4.7.1. Статическая определимость типа
Однако одно ограничение касается всех статических ЯП (в том числе и ЯП Ада) –
его можно назвать статической определимостью типа: тип каждого объекта дол
жен быть определим по тексту программы. Ведь только при этом условии тип мо
жет играть роль основного средства статического прогнозированияконтроля.
Из статической определимости следует, что тип объектов, связанных с какой
либо операцией, определяемой пользователем, не может быть ее динамическим
параметром (а значит, и аргументом), а также не может быть результатом нетри
виальной операции.
Вопрос. Почему?
Подсказка. Все определяемые пользователем операции работают в период испол
нения программы.
Формально сказанное не исключает операций над типами как значениями, од
нако содержательно этими операциями нельзя воспользоваться динамически при
соблюдении принципа статической определимости типа.
4.7.2. Почему высшего порядка?
Статическая определимость типа – необходимое условие статического контроля
типов. Но контролировать типы – значит применять к ним «контролирующие»
операции (при статическом контроле – в период компиляции!). Так что с точки
зрения потребности в контроле тип следует считать объектом высшего порядка
(метаобъектом), который служит для характеристики «обычных» объектов (низ
шего порядка).
4.7.3. Действия с типами
Что же можно «делать» с таким объектом высшего порядка, как тип данных?
Вопервых, можно (и нужно) получать из одних типов другие, то есть преобразо
вывать типы (не объекты данных, а именно типы). Преобразователями типов слу
жат конструкторы типов. Так, конструктор производного типа преобразует роди
тельский тип в новый тип, в общем случае ограничивая класс значений и сохраняя

130
набор базовых операций. Конструктор регулярного и комбинированного типов
преобразует типы компонент регулярных и комбинированных объектов в новый
составной тип. Определяющий пакет представляет собой преобразователь объяв
ленных в нем типов, дополняющий набор их базовых операций. Обратите внима
ние, все названные преобразователи типов (иногда говорят «типовые» функции
с ударением на первом слоге) в язык Ада встроены. Они не только предопределе
ны создателями ЯП, но и по виду своего вызова отличаются от обычных функций.
По смыслу это, конечно, функции, аргументами и результатами которых служат
типы. Но синтаксически они функциями не считаются и работают не в период
исполнения, как остальные функции, а в период трансляции (то есть это «стати
ческие» функции).
Таким образом, в преобразователях типов используются все три компоненты
типа. Имя – для идентификации; множества значений и базовых операций – как
аргументы для получения новых множеств значений и операций. Вместе с тем
статическая (то есть ограниченная) семантика преобразователей типа подчерки
вает ограниченность концепции типа в языке Ада (подчеркивает ее «статичность»
и согласуется с ней). Такая ограниченность характерна для всех статических ЯП,
где тип играет роль основного средства статического прогнозированияконтроля.
Вовторых, с помощью типов можно управлять. Тип управляет прогнозирова
ниемконтролем, когда используется в объявлении объектов и спецификации па
раметров. Тип непосредственно управляет связыванием спецификации и тела
процедуры, когда работает принцип перекрытия. Тип управляет выполнением
цикла, когда непосредственно определяет диапазон изменения управляющей
переменной цикла. В перечисленных примерах тип в Аде попрежнему выступает
в качестве аргумента предопределенных конструктов. Пользователь лишен воз
можности ввести, например, новый принцип прогнозированияконтроля, новый
принцип перекрытия или новую разновидность циклов.
Втретьих, тип может служить аргументом функций, вычисляющих отдельные
значения того же самого или другого типа. Это и есть АТРИБУТНЫЕ функции.
Например, функция «первый» вычисляет наименьшее значение заданного диск
ретного типа, функция «длина» вычисляет целочисленное значение – длину диа
пазона заданного дискретного типа. На самом деле аргументом таких функций
служит ПОДТИП (ведь тип – частный случай подтипа). А подтип, как мы видели
в примере с процедурой «минус», может быть связан с объявленным объектом.
Поэтому в качестве аргумента атрибутной функции может выступать не только
тип, но и объект данных, который в таком случае и идентифицирует соответству
ющий подтип.
Так что уже использованные нами функции «нигр» и «вегр» считаются атри
бутными – их аргументами может быть любой регулярный подтип. Обозначения
всех атрибутных функций предопределены в Аде. Чтобы отличать их от обычных
функций (точнее, чтобы объекты высшего порядка – подтипы – не оказывались
в синтаксической позиции динамических аргументов), и применяется специфи
ческая запись вызова атрибутных функций – аргументподтип отделяют апо
строфом.
Современное состояние языков программирования
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
