Основы объектно-ориентированного проектирования

Ограниченная универсальность


Примеры ограниченной универсальности будут включать подпрограмму и пакет, как и в предыдущем случае.

Предположим, необходима универсальная функция для вычисления минимального из двух значений. Можно попробовать привлечь шаблон swap:

generic type G is private; function minimum (x, y: G) return G is begin if x <= y then return x; else return y; end if; end minimum;

Такое объявление функции имеет смысл только для таких типов G, для которых определена операция сравнения "<=". При статическом контроле типов соответствие этому требованию необходимо проверить на этапе компиляции, не дожидаясь выполнения. Нужен способ проверки того, поддерживается ли данная операция для типа G.

В Ada сама операция <= трактуется как родовой параметр. Синтаксически, операция - это функция, которую можно вызывать, используя обычную инфиксную форму, если в объявлении ее имя размещено в двойных кавычках - "<= ". Следующее объявление становится допустимым в Ada, объединив интерфейс и реализацию.

generic type G is private; with function "<=" (a, b: G) return BOOLEAN is <>; function minimum (x, y: G) return G is begin if x <= y then return x; else return y end if; end minimum;

Ключевое слово with вводит родовые параметры, представляющие подпрограммы, аналогичные "<=".

Родовое порождение minimum можно выполнить для любого типа T1, если для него определена функция T1_le с сигнатурой: function (a, b: T1) return BOOLEAN.

function T1_minimum is new minimum (T1, T1_le);

Если функция T1_le действительно называется "<=", точнее, если ее название и сигнатура соответствуют шаблону, то ее включение в список фактических параметров не требуется. Так, поскольку тип INTEGER имеет предопределенную функцию "<=" с правильной сигнатурой, то можно просто объявить:

function int_minimum is new minimum (INTEGER);

Такое использование заданных по умолчанию подпрограмм с соответствующими именами и типами возможно благодаря предложению is <> в объявлении формальной подпрограммы.
Разрешенная и фактически поощряемая в Ada перегрузка операций играет существенную роль, и функция "<=" определена для различных типов.

От обсуждения ограниченной универсальности для подпрограмм легко перейти к пакетам. Предположим, что требуется универсальный пакет для работы с матрицами объектов любого типа G, где над матрицами определены операции суммирования и умножения. Такое определение имеет смысл, только если эти операции определены для типа G и каждая из этих операций имеет нулевой элемент. Эти свойства необходимы для реализации операций над матрицами. Интерфейсная часть пакета может быть написана следующим образом:

generic type G is private; zero: G; unity: G; with function "+"(a, b: G) return G is <>; with function "*"(a, b: G) return G is <>; package MATRICES is type MATRIX (lines, columns: POSITIVE) is private; function "+"(m1, m2: MATRIX) return MATRIX; function "*"(m1, m2: MATRIX) return MATRIX; private type MATRIX (lines, columns: POSITIVE) is array (1 .. lines, 1 .. columns) of G; end MATRICES;Вот типичные родовые порождения:

package INTEGER_MATRICES is new MATRICES (INTEGER, 0, 1); package BOOLEAN_MATRICES is new MATRICES (BOOLEAN, false, true, "or", "and");Для типа INTEGER опущены фактические параметры + и *, поскольку определены соответствующие операции. Однако их пришлось явно указать в случае BOOLEAN. (Параметры, опускаемые по умолчанию, лучше всего помещать в конец списка формальных параметров.)

Интересно рассмотреть реализацию такого пакета:

package body MATRICES is ... Остальные объявления ... function "*"(m1, m2: G) is result: MATRIX (m1'lines, m2'columns); begin if m1'columns /= m2'lines then raise incompatible_sizes; end if; for i in m1'RANGE(1) loop for j in m2'RANGE(2) loop result (i, j):= zero; for k in m1'RANGE(2) loop result (i, j):= result (i, j) + m1 (i, k) * m2 (k, j) end loop; end loop; end loop; return result end "*"; end MATRICES;В этом фрагменте использованы некоторые специфические особенности Ada:



  • Для параметризованных типов, подобных MATRIX (lines, columns: POSITIVE), объявление переменной должно сопровождаться фактическими параметрами, например mm: MATRIX (100, 75). Далее можно получить их значения, используя нотацию с апострофом: mm'lines в этом случае имеет значение 100.
  • Если a - массив, то a'RANGE(i) обозначает диапазон значений в его i-ом измерении; например, m1'RANGE(1) в приведенном примере - то же самое, что и 1.. m1'lines.
  • Если перемножаются две несовместимые по размерности матрицы, то возбуждается исключение.
Приведенные примеры демонстрируют реализацию ограниченной универсальности в Ada. Они также показывают серьезные ограничения этой техники: выразимы только синтаксические ограничения. Программист может потребовать только существования некоторых подпрограмм (<=, +, *) с заданной сигнатурой, но, если эти подпрограммы не удовлетворяют семантическим ограничениям, эти объявления становятся бессмысленными. Функция minimum имеет смысл, только если <= является отношением полного порядка на G. Для родового порождения MATRICES с заданным типом G, следует быть уверенным, что операции + и * имеют не только сигнатуру G x G
G, но обладают и подходящими свойствами - ассоциативности, дистрибутивности, имеют нулевой элемент. Мы можем использовать математический термин "кольцо" для структур, обладающих этими свойствами.


Содержание раздела