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

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

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Данные и типы
131
Еще одна, четвертая возможность использовать тип как аргумент – настройка
РОДОВЫХ СЕГМЕНТОВ.
Вопрос. Можно ли атрибутную функцию запрограммировать на Аде?
Ответ. Если не использовать других атрибутных функций, то в общем случае
нельзя – нет примитивных операций над типами. Некоторые атрибутные функции
можно выразить через другие с помощью родовых сегментов.
4.8. Родовые (настраиваемые) сегменты
Мы уже отмечали, что тип в Аде не может быть динамическим параметром. Одна ко статическая определимость типов не противоречит статической же обработке заготовок пакетов и процедур, с тем чтобы настраивать их (в период трансляции) на конкретные значения так называемых РОДОВЫХ («СТАТИЧЕСКИХ») ПАРАМЕТРОВ.
Статическими параметрами заготовок пакетов и процедур (РОДОВЫХ СЕГ
МЕНТОВ) могут поэтому быть и типы, и процедуры. Определяя родовой сег мент, можно ввести абстракцию, пригодную для использования в различных кон кретных контекстах.
В Аде имеется мощный аппарат статической абстракцииконкретизации – ап
парат родовых сегментов. Приведем пример родового пакета с четырьмя родовы ми параметрами. После ключевого слова generic следуют четыре спецификации родовых параметров:
generic
type элемент is private;
-- допустим, любой тип (кроме ограниченного приватного) type индекс is(< >);
-- допустим, любой дискретный тип type вектор is array(индекс) of элемент;
-- любой регулярный тип, но имя типа индексов указывать обязательно нужно! with function сумма(X, У: элемент) return элемент;
-- закончился список из четырех формальных родовых параметров, последний параметр – формальная функция «сумма», применимая к объектам формального типа «элемент»
package на_векторах is – «обычная» спецификация пакета
function сумма(А, В: вектор) return вектор; function сигма(А: вектор) return элемент;
end на векторах;
Обратите внимание, здесь одна функция «сумма» – формальный родовой па
раметр, другая – обычная функция, объявленная в спецификации пакета. Как пи сать тело такого пакета, должно быть понятно:
package body на_векторах is
function сумма(A,B: вектор) return вектор is
132
Z: вектор;
begin
for j in вектор'нигр .. вектор'вегр loop
Z(j) :=сумма (A(j), B(j)); end loop; return Z;
end сумма; function сигма(А: вектор) return элемент is
Z: элемент := А (вектор'нигр);
begin
for j in вектор'нигр + 1 .. вектор'вегр loop
Z :=сумма(Z, A(j)); end loop; return Z;
end сигма;
end на_векторах;
Современное состояние языков программирования
Вот возможная конкретизация этого пакета:
package на_целых_векторах is new на_векторах (INTEGER, день, ведомость, '+');
Здесь тип «ведомость» считается введенным объявлением
tуре ведомость is array(день range < >) of INTEGER;
a '+' – предопределенная операция для целых.
Так что если
Т: ведомость(Вт..Пт) := (25,35,10,20); R: ведомость(Вт..Пт) := (10,25,35,15);
то в соответствующем контексте
сумма(T,R) = (35,60,45,35); сигма(Т) = 90; сигма(R) = 85;
Обратите внимание на применение агрегатов, а также на то, как в них исполь
зуется линейный порядок на дискретных типах.
Родовые аргументы должны строго соответствовать спецификации родовых параметров. За этим ведется строгий контроль. Так, функция '+' подошла, а, на пример, «or» или тем более «not» не подойдет (почему?).
Замечание. Обратите внимание на нарушение принципа целостности объектов в аппарате родовых сегментов.
Вопрос. В чем это проявляется? Подсказка. Разве все аргументы конкретизации не связаны еще в объявлении типа
«ведомость»? Зачем же заставлять программиста дублировать эту связь?
Указывать категорию и некоторые другие характеристики родовых парамет ров нужно для контроля за использованием параметра внутри родового сегмента. В нашем случае, например, внутри этого сегмента недопустимы какиелибо опе рации с объектами типа «элемент», кроме явно определяемых программистом (например, «сумма»), а также присваиваний и сравнений. С другой стороны, в качестве типоваргументов в данном случае допустимы любые типы (кроме
Данные и типы
ограниченных приватных). В общем случае в качестве спецификации родового параметра можно указывать категории перечисляемых типов, целых, веществен ных, ссылочных, регулярных, но не комбинированных.
Вопрос. Почему недопустимы комбинированные?
Подсказка. Иначе пришлось бы в родовом сегменте фиксировать названия и типы
полей. Кстати, чем это плохо?
133
4.9. Числовые типы (модель числовых расчетов)
4.9.1. Суть проблемы
Рассматривая основные технологические потребности, невозможно обойти по требность вести числовые расчеты. Эта потребность, как известно, в свое время предопределила само возникновение ЭВМ. Хотя сейчас потребность в числовых расчетах – далеко не самая главная в развитии компьютеров и ЯП, абсолютная потребность в объеме, точности и надежности числовых расчетов продолжает рас ти. Так что ни в одном базовом языке индустриального программирования ее иг норировать нельзя.
Парадоксально, что так называемые машинно независимые языки для науч
ных расчетов (Фортран, Алгол и их диалекты) не предоставили удовлетворитель ной модели числовых расчетов, в достаточной степени независимой от программ ной среды.
В этом их аспекте особенно сказалась несбалансированность средств абстрак
ции и конкретизации. Абстрагироваться от особенностей среды можно, а настро иться на конкретную среду – нельзя. Обеспечить надежность при изменении сре ды – проблема.
Суть в том, что ни в Фортране, ни в Алголе нет возможности явно управлять
диапазоном и точностью представления числовых данных. Можно лишь указать, что требуется «двойная точность» (в некоторых диалектах градаций больше), но какова эта точность, зависит от реализации. Таким образом, пользователь «ма шинно независимого» ЯП оказывается в полной зависимости от конкретной сре ды, если ему нужно гарантировать надежность расчетов.
Одна из причин такой ситуации – в том, что в начале «эры ЭВМ» скорости
числовых расчетов придавалось исключительно большое значение. Поэтому счи талось практически нереальным применять какоелибо представление чисел и какиелибо базовые операции, отличные от непосредственно встроенных в маши ну. Поскольку такие встроенные числовые типы различны на различных ЭВМ, считалось невозможным эффективно решить проблему выбора представления в соответствии с машинно независимыми указаниями пользователя. Поэтому проблема обеспечения надежности числовых расчетов традиционно оставалась вне рамок «машинно независимых» языков.
134
По мере накопления пакетов программ и осознания того факта, что зависи мость от конкретных представлений числовых типов – одно из важнейших пре пятствий при переносе программ из одной вычислительной среды в другую, рос интерес к созданию достаточно универсальной схемы управления числовыми расчетами.
К моменту создания Ады проблема надежности программного обеспечения (в частности, при переносе из одной вычислительной среды в другую) была осоз нана как важнейшая, потеснившая по своей значимости проблему скорости расче тов. К тому же доля числовых расчетов в общем времени исполнения программ существенно сократилась. Появились и методы относительно эффективной реа лизации вычислений с любой заданной точностью (за счет микропрограммирова ния, например).
Все это сделало актуальной и реальной попытку разработать гибкую модель управления числовыми расчетами. Одна из таких моделей воплощена в Аде.
Управлять представлением числовых данных можно и в языке ПЛ/1, и в Кобо ле. Однако в этих ЯП отсутствует явная связь представления данных с гарантией надежности расчетов. В частности, отсутствуют машинно независимые требова ния к точности реализации предопределенных операций над числами. Эти требо вания – основная «изюминка» модели управления расчетами в Аде.
Современное состояние языков программирования
4.9.2. Назначение модели расчетов
Необходимо, чтобы при работе с числовыми типами программист мог гарантиро вать надежность расчетов независимо от целевой среды (если только эта среда пригодна для их выполнения). Гарантировать надежность означает, в частности, гарантировать как необходимую точность расчетов, так и отсутствие незаплани рованных исключительных ситуаций (переполнение, исчезновение порядка) при допустимых исходных данных. Очевидно, что достичь такой цели можно, только предоставив программисту возможность объявлять нужные ему диапазон и точ ность представления чисел.
Искусство авторов языка (создателей модели управления расчетами) прояв ляется в том, чтобы найти разумный компромисс между единообразием управле ния и эффективностью расчетов при гарантированной надежности.
4.9.3. Классификация числовых данных
Начать естественно с разумной классификации числовых данных в зависимости от характера расчетов. В Аде три категории числовых типов: целые, вещественные плавающие и вещественные фиксированные. Для каждой категории типов име ются свои средства объявления.
Целые типы предназначены для точных расчетов, вещественные – для при ближенных. При этом плавающие типы воплощают идею действий с нормализо ванными числами, представленными с относительной погрешностью, зависящей от количества значащих цифр, а фиксированные – идею действий с ненормализо
Данные и типы
ванными числами, представленными с абсолютной погрешностью, зависящей от положения десятичной точки.
Плавающие типы чаще встречаются в ЯП для числовых расчетов. Поэтому ог
раничимся демонстрацией основной идеи управления расчетами на примере пла вающих типов Ады.
135
4.9.4. Зачем объявлять диапазон и точность
Оставим пока в стороне синтаксис и семантику соответствующих объявлений. Ведь объявить диапазон и точность – дело относительно нехитрое. А вот что де лать, когда разрядная сетка или принятое в компьютере представление чисел не соответствует объявлению типа? Важно понимать, что объявление типа остается особенно полезным именно в такой ситуации.
Действительно, в худшем случае исполнитель получает всю информацию, что
бы признать (и информировать пользователя), что он непригоден для такой про граммы. Если объявленные требования к диапазону и точности критичны для предлагаемых расчетов, то такой отказ, безусловно, предпочтительнее, чем трата времени и усилий на заведомо ошибочные расчеты, да еще с риском оставить пользователя в «счастливом» неведении. К тому же легко автоматически или ви зуально выделить фрагменты программы, вызвавшие непригодность исполните ля. Это помогает либо подобрать подходящий исполнитель (автоматически, если доступна, например, неоднородная сеть машин), либо изменить выделенные ком поненты программы.
4.9.5. Единая модель числовых расчетов
Чтобы гарантировать надежность расчетов в любой среде, то есть управлять диа пазоном и точностью на достаточно высоком уровне абстракции, пользователь должен опираться на модель расчетов, единую для всех сред. Однако, стремясь в первую очередь к переносимости программ, нельзя забывать про эффективность конкретизации (вспомните принцип реальности абстракций). Другими словами, единая модель расчетов должна быть гибкой (требовать от конкретных реализа ций минимума свойств и действий).
В Аде абстракция (единство) обеспечивается понятием модельных чисел,
а конкретизация (гибкость) – понятием допустимых чисел.
Совокупность модельных чисел каждого подтипа полностью фиксируется на
уровне, независимом от среды (то есть определяется семантикой ЯП и применяе мым программистом объявлением).
Допустимые числа – это некоторое надмножество модельных чисел, завися
щее от конкретной реализации (рассчитанной на конкретную целевую среду).
Гибкость модели проявляется в том, что расчеты программируются и оценива
ются с помощью модельных чисел, а выполняются с допустимыми числами.
Из набора предопределенных числовых типов реализация имеет право подо
брать для заданного объявления числового типа такой (по возможности мини
136
мальный) базовый тип допустимых чисел, который удовлетворяет указанным при объявлении ограничениям диапазона и точности.
Итак, с одной стороны, все реализации ориентированы на единую «систему координат» – модельные числа; с другой – каждая реализация работает со своими допустимыми числами.
Рассмотрим на примере управление диапазоном модельных чисел. Пусть объявлен плавающий тип
type скорость is digits 8; – число значащих цифр(D) = 8
После ключевого слова digits указан спецификатор точности D (равный в на шем случае восьми), определяющий диапазон модельных чисел типа «скорость» по следующим единым для всех реализаций правилам.
Прежде всего необходимо вычислить количество В двоичных цифр, обеспечи вающих ту же относительную погрешность, что и D десятичных цифр. В общем случае
В = целая_часть(D * ln(10)/ln(2) + 1)
то есть приблизительно 3,3 двоичной цифры для представления одной десятич ной. В нашем случае
 = [ 8 * 3,3 + 1 ] = 27.
Диапазон модельных чисел определяется как совокупность всех (двоичных!) чисел, представимых в виде
знак * мантисса * (2 ** порядок),
где знак – это +1 или –1, мантисса – правильная двоичная дробь, записанная ров но В двоичными цифрами, первая из которых 1 (то есть нормализованная дробь), порядок – целое число между –4*В и +4*В.
Таким образом, с каждой спецификацией D связывается конечный набор ве щественных модельных чисел. При этом фиксируется не только количество цифр в мантиссе, но и максимальный порядок. В нашем случае двоичный порядок равен
27 * 4 = 108.
Соответствующий десятичный порядок 4*D = 32. Чем больше порядок, тем «реже» встречаются модельные числа – точность представления плавающих ти пов относительна.
Современное состояние языков программирования
4.9.6. Допустимые числа
Как уже сказано, диапазон допустимых чисел – это расширение диапазона мо дельных чисел, самое экономичное с точки зрения реализации. В принципе, в на шем случае в качестве допустимых чисел могут фигурировать, например, числа с 40разрядной мантиссой и 7разрядным порядком. Так что для представления такого диапазона допустимых чисел подойдет, например, 48разрядное машинное слово. На практике транслятор подберет в качестве базового для типа «скорость» ближайший из предопределенных плавающих типов REAL, SHORT_REAL, LONG_REAL или иной из плавающих типов, определяемый реализацией.
Данные и типы
137
Уточнить диапазон и точность при объявлении производного типа, числового
подтипа или объекта можно, как обычно, с помощью ограничения. Например:
type высота is new скорость range 0.0 .. 1.0Е5; (высота может меняться от нуля до десяти тысяч). subtype высота_здания is высота range 0.0 .. 1.0ЕЗ; высота_полета : высота digits 5; (для переменной высота_полета допустимая точность меньше, чем в типе «высота»).
4.10. Управление операциями
Управление диапазоном и точностью для плавающих типов имеется во многих ЯП. Однако этого мало для надежного и независимого от среды управления рас четами, если программист лишен возможности на уровне модельных чисел учи тывать погрешности, вносимые предопределенными (то есть элементарными арифметическими) операциями. Такой возможности до Ады не предоставлял ни один ЯП. Результаты всех предопределенных операций над допустимыми числа ми определяются в Аде с помощью модельных чисел следующим единым для лю бой среды способом.
Модельным интервалом называется любой интервал между модельными чис
лами. С каждым допустимым числом ассоциируется так называемый связанный модельный интервал – это минимальный модельный интервал, к которому при надлежит рассматриваемое число (для модельных чисел связанным интервалом считается само модельное число!).
Основное требование к точности реализации предопределенных операций
(модельное ограничение) состоит в том, что результат операций над допустимы ми числами должен попадать в минимальный модельный интервал, содержащий точные математические результаты рассматриваемой операции при изменении аргументов операции в пределах их связанных интервалов.
Наглядно это можно представить следующим рисунком (рис. 4.2).
!!!!!<!!!!!!!!!!!>!!!!!
!!!!!|!!!!!!!!!!!!|!!!!!
!<!|!!!!!!!!!!!!!!|!>!
Модельный интервал аргумента
Точные математические результаты на границе модельного интервала
Допустимый интервал результата. Минимальный объемлю! щий модельный интервал
Рис. 4.2
Таким образом, значениями операций вещественных типов в конкретных реа
лизациях могут быть любые числа, обладающие модельным интервалом, но от клонения результатов операций регламентированы модельным ограничением.
Искусство программиста состоит теперь в том, чтобы гарантировать надеж
ность расчетов, подбирая диапазон и точность в соответствии с условиями задачи.
138
При этом он не должен забывать, что при реализации излишний диапазон или излишняя точность могут стоить дорого и по времени, и по памяти (а могут и пре высить возможности реализации). Короче говоря, нужно программировать как можно ближе к нуждам решаемой задачи.
В Аде предусмотрено, что реализация может предоставлять несколько предоп ределенных (именованных или анонимных) плавающих типов с различными диа пазонами и точностью расчетов.
Искусство автора компилятора проявляется в том, чтобы компилятор был в состоянии подобрать подходящий предопределенный тип (в качестве допусти мых чисел) для любого (или почти любого) объявления типа, с оптимальными затратами на расчеты при полной гарантии соответствия модельному ограни чению.
Обратите внимание, в Аде отдано явное предпочтение двоичным машинам (ведь допустимые числа включают модельные, причем в соответствии с модельным ограничением результат операции над модельным числом должен быть точным, если математический результат оказывается модельным числом; для элементар ных (!) арифметических операций такое требование выполнимо, в отличие от про извольных математических функций). К тому же не любым двоичным машинам, а с достаточно большим порядком во встроенном представлении плавающих чисел (ведь далеко не во всех машинах допустимы порядки, вчетверо превышающие дли ну мантиссы; во всяком случае в БЭСМ6 это не так).
Современное состояние языков программирования
4.11. Управление представлением
Суть проблемы. Единая модель числовых расчетов, как мы видели, позволяет программисту непосредственно управлять представлением данных числовых ти пов в целевой машине. Но потребность управлять представлением возникает не только для числовых типов и не только для данных. Причем требуется управлять в некотором смысле окончательным представлением (как и в случае числовых типов), а не промежуточным (когда, например, данные приватного типа мы пред ставляли данными комбинированного типа). Другими словами, требуется управ лять представлением объектов в терминах понятий, в общем случае выходящих за рамки машинно независимого ЯП и определенных только на конкретной целевой машине.
Такая потребность особенно часто возникает в системных программах, вынуж денных взаимодействовать, в частности, с нестандартными устройствами обмена (вводавывода) или со средствами управления прерываниями целевой машины. Например, требуется явно указать адрес, с которого располагается реализация реакции на конкретное прерывание.
Конечно, программы, использующие управление окончательным (или, как ча сто говорят, «абсолютным») представлением, перестают быть машинно независи мыми. Однако это та мера зависимости от машины (та мера конкретизации), без которой программа неработоспособна.
Данные и типы
139
Вместе с тем это зависимость только от целевой машины. Сами средства управ
ления представлением данных могут оставаться нормальными конструктами ма шинно независимого ЯП, где они играют роль компонент общего аппарата связыва ния, а именно связывания абстрактной спецификации данных с их конкретным абсолютным представлением. Такое связывание выполняется при трансляции и может быть выполнено на любой транслирующей (инструментальной) машине.
С другой стороны, если связывание такого рода выделено как достаточно об
щее языковое понятие и ему соответствует легко идентифицируемый конструкт, то настройка программы (ее перенос) на другую целевую машину сводится к из менению только таких конструктов.
Именно такой идеей и руководствовались авторы ЯП Ада. Управление абсо
лютным представлением выделено в конструкт, который называется УКАЗАНИ ЕМ ПРЕДСТАВЛЕНИЯ (спецификацией представления – representation clauses). Указание представления должно следовать за объявлением тех сущно стей, представление которых в нем конкретизируется. В Аде можно указывать представление для типов, объектов данных, подпрограмм, пакетов и задач, а так же для входов.
Подход к языковым конструктам как компонентам единого аппарата абстракции
конкретизации позволяет легко обнаружить перспективу развития средств управ
ления представлением в Аде. А именно высшим уровнем оформления абстракции
представления было бы выделение специального программного сегмента («модуля
представления»), предназначенного исключительно для описания привязки ма
шинно независимых сегментов к конкретной целевой машине.
Вопросы. Что бы это дало? Каковы недостатки такого решения?
Интересно, что эта перспектива неоднократно рассматривалась автором на лекци
ях за несколько лет до того, как ему стало известно о воплощении модулей управ
ления представлением в системе Кронус (новосибирской модификации Модулы2).
Так что анализ ЯП с самых общих «философских» позиций действительно позво
ляет иногда прогнозировать их развитие.
Рассмотрим несколько примеров одного из самых нужных указаний представ
ления – УКАЗАНИЯ АДРЕСА.
Указание адреса применяют для того, чтобы связать объект данных, подпрог
рамму или вход задачи с той ячейкой памяти целевой машины, которая играет особую роль. Это может быть регистр определенного периферийного устройства; адрес, по которому передается управление при определенном прерывании; ячей ка, состояние которой определяет режим работы машины, и т. п.
Чтобы можно было записать указание адреса, должен быть доступен тип «ад
рес». Содержательно значения этого типа играют роль адресов памяти целевой машины, формально это один из предопределенных типов. В Аде его определяю щим пакетом считается предопределенный пакет «система», характеризующий целевую машину. Следовательно, тип «адрес» становится доступным с помощью указателя контекста вида
with система; use система;
140
Современное состояние языков программирования
Вот примеры указания адреса с очевидным назначением:
for управл_ячейка use at 16#0020#;
после at записана шестнадцатеричная константа типа «адрес». Такое указание ад реса должно быть помещено среди объявлений блока, пакета или задачи после объявления объекта управл_ячейка.
task обработка_прерывания is
entry выполнить;
for выполнить use at 16#40#;
-- вызвать вход «выполнить» – это значит передать управление в ячейку 16#40#
end обработка_прерывания;
Еще примеры использования указаний представления:
слово : constant := 4;
-- элемент памяти – байт, "слово" – из четырех байтов. type состояние is(A,M,W,P);
-- четыре характеристики состояния:
-- каков код символов(ASCII или EBCDIC);
-- разрешены ли прерывания;
-- ждет ли процессор;
-- супервизор или задача type маска_байта is array(0..7) of BOOLEAN; type маска_состояния is array (состояние) of BOOLEAN; type маска_режима is array(1..4) of BOOLEAN; type слово_состояние_программы is record
маска_системы : маска_байта; ключ_защиты : INTEGER range 0..3; состояние_машины : маска_состояния; причина_прерывания : код_прерывания; код_длины_команды : INTEGER range 0..3; признак_результата : INTEGER range 0..3; маска_программы : маска_режима; адрес_команды : адрес;
end record;
-- ниже следует указание представления для этого типа
for слово_состояния_программы use record at mod 8;
-- адреса записей указанного типа
-- должны быть нулями по модулю 8, то есть
-- адресами двойных слов. Далее указаны требования
-- к расположению полей записи относительно ее начала маска_системы at 0 * слово range 0..7;
-- маска системы расположена в первом байте двойного слова. ключ_защиты at 0 * слово range 10..11;
-- разряды 8 и 9 не используются. состояние_машины at 0 * слово range 12.. 15; причина прерывания at 0 * слово range 16..31; код_длины_команды at 1 * слово range 0..1; признак_результата at 1 * слово range 2 ..3;
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]