В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 22
Текст из файла (страница 22)
эффективной была бы абстракция-процедура следующего вида:
procedure минус (X : in out вектор) is
begin
for j in (1..10) loop
X(j) := -X(j) ;
end loop ;
end минус ;
Уже при такой слабой абстракции (только от имени вектора) мы оказались
перед необходимостью согласовывать операционную абстракцию и абстракцию
данных (обратите внимание на диапазон 1..10 и в объявлении функции, и в
объявлении типа). Так что принцип согласования абстракций работает и при
создании языковых конструктов (при создании ЯП, на метауровне), и на уровне
их применения. При этом согласование на метауровне призвано всячески
облегчать согласование на уровне применения.
Вопрос. Нельзя ли упростить последнее в нашем случае?
Подсказка. Следует полнее использовать тип данных.
За счет согласованного объявления управляющей переменной цикла и
диапазона допустимых индексов мы повысили надежность программы. Применяя
функцию "-", невозможно выйти за границы массива - ведь ее аргументами могут
быть только десятиэлементные векторы.
Абстракция от длины вектора (начало). Пойдем дальше по пути абстракции.
Как написать функцию, применимую к вектору любой длины? Уникальность типа
требует снабдить определенным типом каждый параметр. Поэтому возникают два
согласованных вопроса (снова действует принцип согласования абстракций!):
Как объявить нужный тип? Как написать тело процедуры, работающей с массивом
произвольной длины?
Здесь полезно на время оторваться от нашего примера и вспомнить об
общем контексте, в котором эти вопросы возникли.
4.6.4. Квазистатический контроль
Мы продолжаем заниматься в основном данными - одной из трех важнейших
абстракций программирования. Исходя из потребности прогнозирования и
контроля поведения объектов (в свою очередь выводимой из более общей
потребности писать надежные и эффективные программы), мы пришли к концепции
уникальности типа данных. Исходя из назначения системы типов, выделили
динамические, статические и относительно-статические (говоря короче,
квазистатические) ЯП в зависимости от степени гибкости прогнозирования-
контроля. Отметили, что в Аде концепция собственно типа ориентирована на
прогнозирование-контроль статических характеристик поведения объектов, а
концепция подтипа - на прогнозирование-контроль квазистатических (или, если
угодно, квазидинамических) характеристик. Наша ближайщая цель - обосновать
полезность концепции подтипа.
Вспомним классификацию данных и в ней - фактор изменчивости. Он играет
особую роль, так как касается самого динамического атрибута объекта - его
значения.
Поэтому с фактором изменчивости в наибольшей степени связано
противоречие между потребностью гибко изменять поведение объектов и
потребностью прогнозировать это поведение. Другими словами, это противоречие
между потребностью в мощных операционных абстракциях, применимых к весьма
разнообразным данным, и потребностью ограничить их применение, чтобы достичь
надежности и эффективности.
Первая потребность требует свободы, вторая - дисциплины. Чем точнее
удается прогнозировать изменчивость, чем в более жесткие рамки "зажимается"
возможное поведение, тем надежнее контроль, больше возможностей экономить
ресурсы. Но вместе с этим снижается общность, растут затраты на аппарат
прогнозирования-контроля, растут затраты на создание близких по назначению
программ, на их освоение.
Мы уже сталкивались с этим противоречием, когда занимались проблемой
полиморфизма. Но тогда речь шла о небольшом наборе разновидностей (типов)
объектов и противоречие удалось разрешить за счет конечного набора операций
с одним и тем же именем (каждая для своего набора типов объектов). Теперь
нас интересует такая ситуация, когда разновидностей неограниченно много.
Именно такая ситуация создалась при попытке абстрагироваться от длины
вектора в программе "минус".
Абстракция от длины вектора (продолжение). В чем конкретно
противоречие?
С одной стороны, нужна как можно более мощная операционная абстракция,
применимая к векторам любой длины. Для этого необходимо иметь возможность
ввести согласованный с такой абстракцией обобщенный тип, значениями которого
могут быть массивы произвольной длины. Иначе возникнет противоречие с
концепцией уникальности типа (какое?).
Такой тип в Аде объявить можно. Например, так:
type вектор_любой_длины is array (INTEGER range <>) of INTEGER ;
Вместо конкретного диапазона индексов применен оборот вида
тип range <>
который и указывает на то, что объявлен так называемый НЕОГРАНИЧЕННЫЙ
регулярный тип, значениями которого могут быть массивы с любыми диапазонами
индексов указанного типа (в нашем случае - целого).
Аналогичный неограниченный регулярный тип с диапазонами индексов
перечисляемого типа вводит объявление
type таблица is array (буква range <>) of INTEGER ;
Значения такого типа могут служить, например, для перекодировки букв в целые
числа.
Упражнение. Напишите соответствующую программу перекодировки.
Вернемся к типу вектор_любой_длины. Как объявлять конкретные объекты
такого типа? Ведь объявление вида
Y : вектор_любой_длины ;
не прогнозирует очень важной характеристики объекта Y - его возможной длины
(другими словами, прогнозу не хватает точности с точки зрения распределения
ресурсов программы).
Поэтому само по себе такое объявление не позволяет ни обеспечить
эффективность (нельзя распределить память при трансляции), ни настроить
функцию на конкретный аргумент такого типа. И, конечно, раз длина вектора не
объявлена, то нет оснований контролировать ее (например, в процессе
присваивания).
4.6.5. Подтипы
Чтобы разрешить указанное противоречие, авторы Ады были вынуждены
ввести концепцию ПОДТИПА (специально для квазистатического прогнозирования-
контроля изменчивости объектов). Основная идея в том, чтобы при
необходимости можно было, с одной стороны, удалить некоторые атрибуты
объектов из сферы статического прогнозирования-контроля. не указывая их при
объявлении типа. С другой стороны, оставить эти атрибуты для динамического
прогнозирования-контроля с помощью подтипов.
Подтип представляет собой сочетание ТИПА и ОГРАНИЧЕНИЯ на допустимые
значения этого типа. Значения, принадлежащие подтипу, должны, во-первых,
принадлежать классу значений ограничиваемого типа и, во-вторых,
удовлетворять соответствующему ОГРАНИЧЕНИЮ.
Подтип можно указывать при объявлении объектов. Например,
A : вектор_любой_длины (1..10) ;
объявляет десятиэлементный вектор A (причем использовано так называемое
ОГРАНИЧЕНИЕ ИНДЕКСОВ);
выходной : день_недели range сб..вс ;
объявляет объект типа день_недели, принимающий значение либо "сб", либо "вс"
(причем применяется так называемое ОГРАНИЧЕНИЕ ДИАПАЗОНА).
Бывают и другие виды ограничений (для вещественных и вариантных
комбинированных типов).
Раньше мы говорили, что объявление объекта связывает с ним некоторый
тип. На самом деле правильнее сказать, что оно связывает с ним некоторый
подтип. Когда ограничение отсутствует, то все значения типа считаются
удовлетворяющими подтипу.
Подтип, указанный в объявлении объекта, характеризует его во всей
области действия объекта, в течение всего периода его существования. Поэтому
становится возможным, во-первых, учитывать подтип при распределении памяти
для объекта (например, для массива A выделить ровно десять квантов памяти);
во-вторых, контролировать принадлежность подтипу при присваивании. Последнее
приходится иногда делать динамически (поэтому и идет речь о
"квазистатическом контроле"). Это может замедлять работу программы, зато
повышает надежность.
Пусть, например, объявлены объекты
A,B : вектор_любой_длины (1..10) ;
выходной : день_недели range сб..вс ;
праздник : день_недели ;
день_рождения : день_недели ;
C,D : вектор_любой_длины (1..11) ;
будний_день : день_недели range пн..пт ;
учебный_день : день_недели range пн..сб ;
Тогда присваивания
A := B; B := A; праздник := день_рождения;
день_рождения := будний_день;
праздник := выходной;
C := D; D := С;
не потребуют никакой дополнительной динамической проверки, так как
допустимые значения выражений в правой части присваивания всегда
удовлетворяют подтипам объектов из левой части.
Присваивания
A := C; C := A; A := D; B := D; D := A; D := B;
будний_день := выходной;
выходной := будний_день;
также не требуют дополнительной динамической проверки - они всегда
недопустимы, и обнаружить это можно статически, при трансляции.
А вот присваивания
будний_день := учебный_день;
будний_день := праздник;
учебный_день := выходной;
учебный_день := праздник;
нуждаются в динамической проверке (почему?).
4.6.6. Принцип целостности объектов
Вернемся к нашей процедуре "минус", вооруженные концепцией подтипа.
Допустим, что ее параметр станет типа вектор_любой_длины. Как обеспечить
настройку на конкретный вектор-аргумент? Другими словами, абстракцию мы
обеспечили (есть обобщенный тип), а вот реальна ли она (чем обеспечена
конкретизация)?
Вспомним, как это делается в Алголе 60 или Фортране. Границы
конкретного массива-аргумента нужно передавать обрабатывающей процедуре в
качестве дополнительных аргументов. Это и неудобно, и ненадежно (где
гарантия, что будут переданы числа, совпадающие именно с границами нужного
массива?).
Другими словами, перед нами пример нарушения целостности объекта.
Состоит оно в том, что цельный объект-массив при подготовке к передаче в
качестве параметра приходится разбивать на части (имя - отдельно, границы -
отдельно), а в теле процедуры эти части "собирать" (к тому же при полном
отсутствии контроля - ведь транслятор лишен информации о связи между
границами и именем массива; знает о ней лишь программист).
Создатели более современных ЯП руководствуются принципом (сохранения)
целостности объектов. Суть его в том, что ЯП должен обеспечивать возможность
работать с любым объектом как с единым целым (не требуя дублировать
характеристики объекта и тем самым устраняя источник ошибок). Более точно
этот принцип можно сформулировать так :
вся необходимая информация об объекте должна быть доступна через его
имя.
Соблюдение принципа целостности требует включения в базис
соответствующих средств доступа к характеристикам объектов. В Аде, где этот
принцип положен в основу языка, предопределены универсальные функции,
позволяющие узнавать атрибуты конкретных объектов по именам этих объектов.
Они так и называются - АТРИБУТНЫЕ ФУНКЦИИ. Тот или иной набор
атрибутных функций связывается с объектом в зависимости от его типа. В
частности, для объектов регулярного типа определены атрибутные функции
нигр(k) и вегр(k), сообщающие нижнюю и верхнюю границы диапазона индексов по
к-му измерению. Например,
A'нигр(1) = 1, B'нигр(1) = 1,
A'вегр(1) = 10, C'нигр(1) = 1,
D'вегр(1) = 11.
Абстракция от длины вектора (окончание). Теперь совершенно ясно, как
объявить процедуру "минус", применимую к любому массиву типа
вектор_любой_длины.
procedure минус (X : in out вектор_любой_длины) is
begin
for j in (X'нигр(1)..X'вегр(1)) loop
X(j) := -X(j) ;
end loop ;
end минус ;
Для одномерных массивов вместо нигр(k) и вегр(k) можно писать короче -
нигр и вегр, так что заголовок цикла может выглядеть красивей
for j in (X'нигр..X'вегр) loop .
Итак, мы полностью справились с нашей второй абстракцией. При этом
воспользовались принципом целостности, чтобы обеспечить реальность
абстракции. Со стороны данных для этого понадобилось ввести неограниченные
типы и ограничения-подтипы, а со стороны операций - атрибутные функции
(опять потребовалось согласовывать абстракции!).
[Сочетание относительно "свободных" типов с постепенным ограничением
изменчивости вплоть до полной фиксации значений объектов (когда они
становятся константами) широко применяется при прогнозировании-контроле
поведения объектов в Аде. Подтип играет роль, аналогичную той роли, которую