[x]. в роли фактических родовых параметров могут выступать лишь типы, совместимые с CONSTRAINING_TYPE;
[x]. в классе C над сущностью типа G допускаются только те операции, которые допускаются над сущностью CONSTRAINING_TYPE, другими словами, представляющими собой компоненты базового класса этого типа.
Какое родовое ограничение использовать для класса VECTOR? Обсуждая множественное наследование, мы ввели в рассмотрение NUMERIC - класс объектов, допускающих базисные арифметические операции: сложение и умножение с нулем и единицей (лежащая в его основе математическая структура называется кольцом). Эта модель кажется вполне уместной, хотя нам необходимо пока только сложение. Соответственно, класс будет описан так:
indexing
description: "Векторы, допускающие сложение"
class
VECTOR [G -> NUMERIC]
... Остальное - как и раньше (но теперь правильно!) ...
После чего ранее некорректная конструкция в теле цикла
Result.put(item (i) + other.item (i), i)
становится допустимой, поскольку item (i) и other.item (i) имеют тип G, а значит, к ним применимы все операции NUMERIC, включая, инфиксный "+".
Следующие родовые порождения корректны, если полагать, что все классы, представленные как фактические родовые параметры, являются потомками NUMERIC:
VECTOR [NUMERIC]
VECTOR [REAL]
VECTOR [COMPLEX]
Класс EMPLOYEE не порожден от NUMERIC, так что попытка использовать VECTOR [EMPLOYEE] приведет к ошибке времени компиляции.
Абстрактный характер NUMERIC не вызывает никаких проблем. Фактический параметр при порождении может быть как эффективным (примеры выше), так и отложенным (VECTOR [NUMERIC_COMPARABLE]), если он порожден от NUMERIC.
Аналогично описываются класс словаря и класс, поддерживающий сортировку:
class DICTIONARY [G, H -> HASHABLE] ...
class SORTABLE [G -> COMPARABLE] ...
Игра в рекурсию
Вот некий трюк с нашим примером: спросим себя, возможен ли вектор векторов? Допустим ли тип VECTOR [VECTOR [INTEGER]]?
Ответ следует из предыдущих правил: только если фактический родовой параметр совместим с NUMERIC. Сделать это просто - породить класс VECTOR от класса NUMERIC (см. упражнение 16.2):
indexing
description: "Векторы, допускающие сложение"
class
VECTOR [G -> NUMERIC]
inherit
NUMERIC
... Остальное - как и раньше...
Векторы, подобные этому, можно и впрямь считать "числовыми". Операции сложение и умножение дают структуру кольца, в котором роль нуля (zero) играет вектор из G-нулей, и роль единицы (unity) - вектор из G-единиц. Операция сложения в этом кольце - это, строго говоря, векторный вариант infix "+", речь о котором шла выше.
Можно пойти дальше и использовать VECTOR [VECTOR [VECTOR [INTEGER]]] и так далее - приятное рекурсивное приложение ограниченной универсальности.
И снова неограниченная универсальность
Конечно же, не все случаи универсальности ограничены. Форма - STACK [G] или ARRAY [G] - по-прежнему существует и называется неограниченной универсальностью. Пример DICTIONARY [G, H -> HASHABLE] показывает, что класс одновременно может иметь как ограниченные, так и неограниченные родовые параметры.
Изучение ограниченной универсальности дает шанс лучше понять неограниченный случай. Вы, конечно же, вывели правило, по которому class C [G] следует понимать как class C [G -> ANY]. Поэтому если G - неограниченный типовой параметр (например, класса STACK), а x - сущность, имеющая тип G, то мы точно знаем, что можем делать с сущностью x: читать и присваивать значения, сравнивать (=, /=), передавать как параметр и применять в универсальных операциях clone, equal и прочее.
Попытка присваивания
Наша следующая техника адресуется к тем областям Объектной страны, в которых из страха тиранического поведения мы не можем позволить править простым правилам типизации, не встречая никакого сопротивления.
Когда правила типов становятся несносными
Цель правил типов, введенных вместе с наследованием, в достижении статически проверяемого динамического поведения, так чтобы система, прошедшая проверку при компиляции, не выполняла неадекватных операций над объектами во время выполнения.
Вот два основных правила, представленных в первой лекции о наследовании (лекция 14).
[x]. Правило Вызова Компонентов: запись x.f осмысленна лишь тогда, когда базовый класс x содержит и экспортирует компонент f.
[x]. Правило Совместимости Типов: при передаче a как аргумента или при присваивании его некой сущности необходимо, чтобы тип a был совместим с ожидаемым, то есть основан на классе, порожденным от класса сущности.
Правило Вызова Компонентов не является причиной каких-либо проблем - это фундаментальное условие всякой работы с объектами. Естественно, что обращаясь к компоненту объекта, нужно проверить, действительно ли данный класс предлагает и экспортирует данный компонент.
Правило Совместимости Типов требует больше внимания. Оно предполагает наличие у нас всей информации о типах объектов, с которыми мы работаем. Как правило, это так, - создав объекты, мы знаем, чем они являются, но иногда информация может частично отсутствовать. Вот два таких случая.
[x]. В полиморфной структуре данных мы располагаем лишь информацией, общей для всех объектов структуры; однако нам может понадобиться и специфическая информация, применимая только к отдельному объекту.
[x]. Если объект приходит из внешнего мира - файл или по сети - мы обычно не можем доверять тому, что он принадлежит определенному типу.
Давайте займемся исследованием примеров этих двух случаев. Рассмотрим для начала полиморфную структуру данных, такую как список геометрических фигур:
figlist: LIST [FIGURE]