Скачиваний:
82
Добавлен:
02.05.2014
Размер:
2.28 Mб
Скачать

19.7. Операторы, версии и сигнатуры

Как утверждалось в разделе 19.3, любой заданный оператор может иметь много раз- личных скрытых версий реализации (что известно также как явная специализация). Это означает, что по мере продвижения в иерархии типов от некоторого супертипа Т к неко- торому подтипу Т' необходимо, чтобы по крайней мере существовала возможность пе- реписать реализацию (по разным соображениям) операторов типа Т для типа Т'. В каче- стве примера рассмотрим следующий оператор MOVE.

OPERATOR MOVE ( Е ELLIPSE, R RECTANGLE ) RETURNS ( ELLIPSE ) VERSION ER_M0VE ;

RETURN ( ELLIPSE ( THE_A ( E ), THE_B ( E }, R_CTR ( R ) ) ; END OPERATOR j

Оператор MOVE выполняет "перемещение" эллипса Е таким образом, что его центр помещается в центр прямоугольника R. Точнее говоря, он возвращает эллипс, который в точности соответствует эллипсу, указанному его параметром Е, но центр возвращае- мого эллипса совпадает с центром прямоугольника, заданного параметром R. Обратите внимание, что фраза VERSION во второй строке определения вводит еще одно имя, ER_M0VE, используемое именно для конкретной версии оператора MOVE (подробности приводятся в следующем разделе). Кроме того, обратите внимание, что неявно пред- полагается существование оператора R_CTR, который возвращает координаты центра заданного прямоугольника.

А теперь предположим, что существует механизм явной специализации, т.е. требует- ся определить другую версию оператора MOVE, выполняющего перемещение окружно- стей, а не эллипсов7.

OPERATOR MOVE ( С CIRCLE, R RECTANGLE ) RETURNS ( CIRCLE ) VERSION CR_M0VE ;

RETURN ( CIRCLE ( THE R ( С ), R_CTR ( R ) ) ; END OPERATOR ;

Аналогично можно было бы получить явную специализацию для оператора MOVE (скажем, ES MOVE), если бы аргументы имели соответственно конкретные типы ELLIPSE и SQUARE, а аргументы конкретных типов CIRCLE и SQUARE, например, — CS_M0VE.

Сигнатуры

Термин "сигнатура" означает сочетание имени некоторого оператора и типов опе- рандов данного оператора. (Однако отметим, что у разных авторов и в разных языках программирования в этот термин вкладывается несколько отличный смысл. Например, иногда считается, что к сигнатуре относится тип результата, а иногда— имена операн- дов и результата.) Поэтому необходимо уточнить различия между некоторыми указан- ными ниже понятиями.

  1. Различие между аргументами и параметрами.

  2. Различие между объявляемыми типами и реальными (конкретными) типами.

  3. Различие между операторами с точки зрения пользователей и операторами с точки зрения системы (имеются в виду явные специализации или скрытые версии этих операторов, как уже объяснялось выше).

Фактически мы различаем (хотя в литературе часто этого не делают!) по крайней ме- ре три вида сигнатур, связанных с данным оператором Ор.

■ Одна сигнатура определения, которая состоит из имени оператора Ор вместе с объявляемыми типами его параметров в том порядке, в котором параметры указаны в предоставленном пользователю определении данного оператора.

Эта сигнатура соответствует оператору Ор с точки зрения пользователя. На- пример, сигнатурой определения оператора MOVE будет просто сигнатура MOVE(ELLIPSE, RECTANGLE)1!.

  • Набор сигнатур версий, по одной для каждой явной специализации или версии реализации оператора Ор, в каждой из которых содержатся имя оператора Ор вме- сте с объявленными типами его параметров в том порядке, в котором эти пара- метры определены для данной версии. Такие сигнатуры соответствуют различным частям кода скрытой реализации оператора Ор. Например, сигнатурой версии CR_M0VE оператора MOVE будет сигнатура MOVE(CIRCLE, RECTANGLE).

  • Набор сигнатур вызова, по одной для каждого возможного сочетания конкрет- ных типов аргументов, в каждой из которых содержатся имя оператора Ор вместе с соответствующим сочетанием конкретных типов аргументов, взятых по порядку. Эти сигнатуры соответствуют различным возможным вариантам вызова операто- ра Ор (в соотношении "один ко многим", поскольку одна сигнатура может соот- ветствовать многим различным вызовам). Пусть, например, переменные Е и R имеют соответственно конкретные типы CIRCLE и SQUARE. Тогда для вызова опе- ратора MOVE (Е, R) будет применяться сигнатура MOVE (CIRCLE, SQUARE).

Таким образом, различные сигнатуры вызова используют один и тот же оператор, соответствующий, по крайней мере потенциально, различным версиям реализации данного оператора (т.е. различным скрытым специализациям). Поэтому если не- сколько версий "одного и того же оператора" выполняются фактически скрыто, значит, выбор каждой версии, вызываемой в любом конкретном случае, будет за- висеть от того, какая из версий сигнатуры "лучше подходит" в качестве допусти- мой сигнатуры вызова. Процесс выбора наиболее подходящей версии, т.е. реше- ние о том, какую версию необходимо вызывать, — это, конечно, процесс связыва- ния во время выполнения, как уже отмечалось в разделе 19.3.

Кстати, отметим, что сигнатуры определения — это понятие собственно модели; сиг- натуры версий — это понятие, относящееся лишь к реализации; сигнатуры вызова — это, хотя в некотором смысле и понятие модели, на самом деле, как и понятие заменимости, непосредственное логическое следствие, в первую очередь, основной идеи наследования типов. В действительности тот факт, что возможно существование различных сигнатур вызова, является просто частью концепции заменимости.

В [3.3] утверждается, что необходимо иметь возможность осуществлять разделение между сигнатурой определения для данного оператора и определениями всех реализащш (версий) этого опе- ратора. Основное назначение такого разделения — поддержка фиктивных типов (называемых также "абстрактными" и "жземпляронеобразующими" типами, или иногда— просто "интерфейсами"), т.е. типов, которые совсем не имеют конкретного типа какого-либо значения. Эти типы позволяют определять операторы, которые применимы к нескольким различным обычным типам, являющимся собственными подтипами фиктивного типа. Любой из таких операторов затем может быть явно специализирован, т.е. может быть явно определена соответствующая версия оператора для каждого из этих обычных подтипов. В контексте нашего примера тип PLANE FIGURE в указанном выше смыс- ле мажет быть определен как фиктивный тип, после чего сигнатура определения оператора AREA может быть сформулирована на уровне типа PLANE_FIGURE, а явные версии реализации определены для конкретных типов ELLIPSE POLYGON и т.д. ,

Операторы чтения и обновления

До сих пор мы негласно подразумевали, что оператор MOVE предназначен лишь для чтения. Однако давайте предположим, что он был определен как оператор обновления.

OPERATOR MOVE ( Е ELLIPSE, R RECTANGLE ) UPDATES { E ) VERSION ER MOVE ; BEGIN ;

THE CTR { E ) := R CTR ( R ) ; END ; ~ END OPERATOR ;

(Напомним, что операторы чтения и обновления иногда называют соответственно на- блюдателями и мутаторами. См. главу 5, в которой описаны различия между ними.)

Заметим, что в результате вызова этой версии оператора MOVE будет изменен его первый аргумент (проще говоря, будет "изменен центр" заданного аргумента) и что обновление выполняется независимо от того, к чему относится первый аргумент: к конкретному типу ELLIPSE либо к конкретному типу CIRCLE. Иначе говоря, здесь явная специализация для ок- ружностей не требуется8. Следовательно, преимущество операторов обновления в общем случае заключается в том, что они могут освободить нас от необходимости явно записы- вать определенные операторы специализации. В частности, обратите внимание на послед- ствия в отношении сопровождения программ. Например, что случится, если впоследствии потребуется ввести тип 0_CIRCLE, являющийся подтипом типа CIRCLE?

Изменение семантики оператора

Как мы убедились, при прохождении вниз по иерархии типов всегда можно переопре- делить реализацию оператора, что имеет одно важное следствие. Появляется вероятность изменения семантики данного оператора. Например, может случиться так, что реализация оператора AREA для типа CIRCLE реально возвращает, скажем, длину данной окружности вместо площади. (Тщательное проектирование типов может помочь в некоторой степени смягчить эту проблему. Например, если оператор AREA определен как возвращающий ре- зультат типа AREA, то очевидно, что никакая реализация не сможет возвращать результат типа LENGTH. Однако она может возвращать неверное значение площади!)

Более того, хотя это покажется неожиданным, можно даже утверждать (и на самом деле так утверждают), что подобное изменение семантики может быть желательным. Пусть, например, тип T0LL_HIGHWAY (Платная дорога)— собственный подтип типа HIGHWAY (Дорога), и пусть оператор TRAVELJTIME (Время проезда) вычисляет время, за- трачиваемое на движение между двумя заданными пунктами на указанной дороге. Для платной дороги оно будет вычисляться по формуле (d/s) + (n*t), где d — расстояние, s — скорость, п — количество касс для оплаты и t — время, затрачиваемое на оплату проезда возле каждой кассы. Для бесплатной дороги время вычисляется, как частное d/s.

Приведем еще и обратный пример, т.е. пример, когда изменение семантики, безус- ловно, «ежелательно. Снова рассмотрим эллипсы и окружности. По-видимому, нам бы хотелось, чтобы оператор AREA был определен таким образом, чтобы вычисление площа-

ди данной окружности приводило к одному и тому же результату независимо от того, рассматривается эта окружность именно как окружность или же как эллипс. Другими словами, предположим, что перечисленные ниже события происходят в указанной по- следовательности.

  1. Определяются тип ELLIPSE и соответствующая версия оператора AREA. Предполо- жим для простоты, что код оператора AREA не предусматривает использование ре- ального представления для эллипсов.

  2. Определяется тип CIRCLE как подтип типа ELLIPSE, но пока не определяется от- дельная версия реализации оператора AREA для окружностей.

  3. Вызывается оператор AREA для некоторой конкретной окружности с, чтобы полу- чить результат, помещаемый, например, в переменную areal. Этот вызов исполь- зует, естественно, версию оператора AREA для типа ELLIPSE.

  1. Определяется собственная версия реализации оператора AREA для окружностей.

  1. Вновь вызывается оператор AREA для той же окружности с, чтобы получить резуль- тат, помещаемый, например, в переменную агеа2 (и на этот раз уже используется версия оператора AREA для типа CIRCLE).

Нам бы хотелось, конечно, настаивать на том, что условие areal=area2 должно вы- полняться. Но это возможное требование ничем не обеспечено, поскольку, как уже отме- чалось, всегда существует вероятность того, что версия оператора AREA, реализованная для окружностей, может возвращать, например, длину окружности вместо площади или просто неверное значение площади круга.

Теперь возвратимся к примеру с оператором TRAVELJIIME. На самом деле мы считаем этот пример, как и другие подобные ему, крайне неубедительным, т.е. никак не убеж- дающим нас в том, что в указанных ситуациях может быть желательным изменение се- мантики оператора. Рассмотрим возможные последствия.

  • Если тип TOLL_HIGHWAY является настоящим подтипом типа HIGHWAY, это по определению означает, что каждая отдельная платная дорога является факти- чески дорогой.

  • Следовательно, некоторые дороги (т.е. некоторые значения типа HIGHWAY) являют- ся действительно платными дорогами и имеют кассы для оплаты проезда. Тогда тип HIGHWAY — это не "дороги без касс для оплаты проезда", а "дороги с л кассами для оплаты проезда" (где л может быть равно нулю).

  • Поэтому оператор TRAVELJIIME для типа HIGHWAY не "вычисляет время проезда по дорогам, на которых нет касс", а "вычисляет время проезда d/s по некоторым до- рогам, игнорируя кассы для оплаты проезда".

  • Оператор TRAVELJIIME для типа TOLL_HIGHWAY, напротив, "вычисляет время про- езда (d/s) + (n*t) по некоторым дорогам, не игнорируя кассы для оплаты про- езда". Таким образом, эти два оператора TRAVELJIIME — логически разные опера- торы. Путаница между двумя различными операторами возникает из-за того, что им присвоили одно и то же имя. Фактически здесь мы имеем дело с перегружае- мым, а не с включаемым полиморфизмом.

(В качестве дополнительного замечания отметим, что на практике недоразумения возникают из-за того, что, как это ни печально, многие авторы используют термин пере- гружаемый полиморфизм для включаемого полиморфизма.)

Подведем итоги. Мы по-прежнему не считаем изменение семантики оператора хорошей идеей. Как мы убедились, данное требование не гарантируется, однако, безусловно, можно определить нашу модель наследования таким образом (что мы и делаем), чтобы утвер- ждать, что если семантика изменилась, то реализация нарушена, т.е. данная реализация не является реализацией модели и последствия непредсказуемы. Обратите внимание, что наша точка зрения на этот вопрос (т.е. то, что мы считаем недопустимыми такие измене- ния) имеет еще одно преимущество: независимо от того, определены ли какие-либо явные специализации данного оператора, представление пользователя остается одним и тем же. Иначе говоря, существует оператор (отдельный оператор), называемый Ор; он применим ко всем значениям аргументов некоторого конкретного типа Т и, следовательно, по опреде- лению — к значениям аргумента любого собственного подтипа типа Т.

Соседние файлы в папке Дейт К. Дж. Введение в системы баз данных [7 издание]