Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования. Концепции и принципы

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Данные и типы
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
набор базовых операций. Конструктор регулярного и комбинированного типов преобразует типы компонент регулярных и комбинированных объектов в новый составной тип. Определяющий пакет представляет собой преобразователь объяв ленных в нем типов, дополняющий набор их базовых операций. Обратите внима ние, все названные преобразователи типов (иногда говорят «типовые» функции с ударением на первом слоге) в язык Ада встроены. Они не только предопределе ны создателями ЯП, но и по виду своего вызова отличаются от обычных функций. По смыслу это, конечно, функции, аргументами и результатами которых служат типы. Но синтаксически они функциями не считаются и работают не в период исполнения, как остальные функции, а в период трансляции (то есть это «стати ческие» функции).
Таким образом, в преобразователях типов используются все три компоненты типа. Имя – для идентификации; множества значений и базовых операций – как аргументы для получения новых множеств значений и операций. Вместе с тем статическая (то есть ограниченная) семантика преобразователей типа подчерки вает ограниченность концепции типа в языке Ада (подчеркивает ее «статичность» и согласуется с ней). Такая ограниченность характерна для всех статических ЯП, где тип играет роль основного средства статического прогнозированияконтроля.
Вовторых, с помощью типов можно управлять. Тип управляет прогнозирова ниемконтролем, когда используется в объявлении объектов и спецификации па раметров. Тип непосредственно управляет связыванием спецификации и тела процедуры, когда работает принцип перекрытия. Тип управляет выполнением цикла, когда непосредственно определяет диапазон изменения управляющей переменной цикла. В перечисленных примерах тип в Аде попрежнему выступает в качестве аргумента предопределенных конструктов. Пользователь лишен воз можности ввести, например, новый принцип прогнозированияконтроля, новый принцип перекрытия или новую разновидность циклов.
Втретьих, тип может служить аргументом функций, вычисляющих отдельные значения того же самого или другого типа. Это и есть АТРИБУТНЫЕ функции. Например, функция «первый» вычисляет наименьшее значение заданного диск ретного типа, функция «длина» вычисляет целочисленное значение – длину диа пазона заданного дискретного типа. На самом деле аргументом таких функций служит ПОДТИП (ведь тип – частный случай подтипа). А подтип, как мы видели в примере с процедурой «минус», может быть связан с объявленным объектом. Поэтому в качестве аргумента атрибутной функции может выступать не только тип, но и объект данных, который в таком случае и идентифицирует соответству ющий подтип.
Так что уже использованные нами функции «нигр» и «вегр» считаются атри бутными – их аргументами может быть любой регулярный подтип. Обозначения всех атрибутных функций предопределены в Аде. Чтобы отличать их от обычных функций (точнее, чтобы объекты высшего порядка – подтипы – не оказывались в синтаксической позиции динамических аргументов), и применяется специфи ческая запись вызова атрибутных функций – аргументподтип отделяют апо строфом.
Современное состояние языков программирования
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]