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

19.4. Переменные и операция присвоения

Предположим, что имеются две переменные, Е и С, с объявленными типами ELLIPSE и CIRCLE соответственно.

VAR Е ELLIPSE ; VAR С CIRCLE ;

Сначала проинициализируем переменную С значением некоторой окружности; ска- жем для определенности, окружности с радиусом 3 и центром в начале координат.

С := CIRCLE ( LENGTH ( 3.0 ), POINT ( 0.0, 0.0 )) ;

В правой части оператора присвоения вызывается операция выбора типа CIRCLE. (Напомним, что, как указывалось в главе 5, для каждого объявленного возможного пред- ставления имеется соответствующий оператор выбора с тем же именем и с параметрами, соответствующими компонентам рассматриваемого возможного представления. Опера- тор выбора служит для того, чтобы пользователь мог указать или "выбрать" некоторое значение заданного типа, предоставляя некоторое значение для каждого компонента рас- сматриваемого возможного представления.)

Теперь рассмотрим следующее присвоение.

Е := С ;

Обычно при отсутствии подтипов и наследования операция присвоения требует, чтобы переменная, указанная слева в операции присвоения, и значение, указанное в операции при- своения выражением справа, были одного и того же типа (т.е. одного и того же объявленного типа в случае переменной). Однако из принципа заменимости значения следует, что там, где система предполагает некоторое значение типа ELLIPSE, его всегда можно заменить значени- ем типа CIRCLE, так что показанный выше оператор присвоения является допустимым (фактически это присвоение представляет собой полиморфный оператор). В результате вы- полнения этого оператора окружность из переменной С будет скопирована в переменную Е и, в частности, значение переменной Е после присвоения будет относиться к типу CIRCLE, а не просто к типу ELLIPSE. Иными словами, можно сделать следующие утверждения.

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

Замечание. Ниже, в подразделе "Оператор TREAT DOWN", обсуждается проце- дура получения этого радиуса.

■ Из свойства заменимости следует, что переменная объявленного типа Т может иметь значение, конкретный тип которого будет являться любым подтипом типа Т. Поэтому нужно строго различать объявленный тип данной переменной и ее реальный тип, т.е., говоря точнее, конкретный тип (текущее значение). В сле- дующем подразделе мы возвратимся к этому важному вопросу.

Чтобы продолжить рассмотрение примера, предположим, что у нас есть и другая пе- ременная А с объявленным типом AREA.

VAR A AREA ;

Рассмотрим приведенное ниже присвоение. А := AREA ( Е ) ;

В данном случае произойдет следующее.

  • Во-первых, во время компиляции система выполнит проверку типа в выражении AREA(E). Проверка даст положительный результат, поскольку переменная Е имеет объявленный тип ELLIPSE и единственный параметр оператора AREA также имеет объявленный тип ELLIPSE (см. раздел 19.2).

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

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

Скалярные переменные

Мы уже видели, что текущее значение v скалярной переменной V с объявленным ти- пом Т может относиться к любому подтипу типа Т, равно как и к самому данному типу. Поэтому можно представить модель V в виде упорядоченной тройки вида <DT,MST,v>. Указанные параметры имеют следующий смысл.

  • DT — объявленный тип переменной V.

  • MST - текущий конкретный тип переменной V.

■ v - значение конкретного типа MST, а именно — текущее значение переменной V. Мы используем обозначения DT(V), MST(V) и v(V) соответственно для компонентов

DT, MST и v модели скалярной переменной V. Отметим, что MST(V), конечно же, всегда является подтипом, хотя и необязательно действительным подтипом наподобие DT(V); в общем случае MST(V) и v(V) изменяются во времени; в действительности MST(V) следует из v( V), поскольку каждое значение относится ровно к одному конкретному типу.

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

■ Выражение X имеет объявленный тип, DT(X) (точнее говоря, его имеет результат вычисления выражения X), производный очевидным образом от объявленных типов операндов выражения X (включая объявленные типы результатов любых вызовов операторов, содержащихся в выражении X) и известный во время компиляции.

■ Выражение X также имеет текущий конкретный тип, MST(X) (точнее говоря, его имеет результат вычисления выражения X), производный очевидным образом от текущих значений операндов выражения X (включая текущие значения результа- тов любых вызовов операторов, содержащихся в выражении X) и в общем случае неизвестный до времени выполнения.

Теперь можно точно описать выполнение операции присвоения, Рассмотрим сле- дующую операцию.

V := X ;

Здесь V— скалярная переменная, а X— скалярное выражение. Тип DT(X) должен быть подтипом типа DT(V), иначе присвоение недопустимо (эта проверка выполняется во время компиляции). Если присвоение допустимо, то в результате тип MST(V) станет рав- ным MST(X), а тип v(V) станет равным v(X).

По ходу заметим, что если текущий конкретный тип переменной V — это тип Т, то каждый действительный супертип типа Т является также "текущим типом" переменной V. Например, если переменная Е (объявленного типа ELLIPSE) имеет текущее значение, от- носящееся к конкретному типу CIRCLE, то типы CIRCLE, ELLIPSE и PLANE_FIGDRE все яв- ляются "текущими типами" переменной Е. Однако выражение "текущий тип X" обычно применяют, подразумевая, по крайней мере неформально, именно типМЭТ(Х).

Пересмотр понятия заменимости

Рассмотрим следующее определение оператора.

OPERATOR COPY ( Е ELLIPSE ) RETURNS ( ELLIPSE ) ;

RETURNS { E ) ; END OPERATOR ;

Благодаря свойству заменимости оператор COPY может, очевидно, быть вызван с ар- гументом конкретного типа (либо ELLIPSE, либо CIRCLE) и возвратит результат, относя- щийся к тому же конкретному типу. Отсюда следует, что понятие заменимости имеет еще одно следствие: если оператор Ор определен и его результат имеет объявленный тип Т, то реальный результат выполнения оператора Ор может относиться к любому подтипу типа Т (в общем случае). Другими словами, как ссылка на переменную объяв- ленного типа Т может в действительности обозначать некоторое значение любого под- типа типа Т (в общем случае), так и вызов оператора с объявленным типом результата Т может фактически возвращать некоторое значение любого подтипа типа Т (опять-таки, в общем случае).

Оператор TREAT DOWN

Еще раз рассмотрим пример, приведенный в начале данного раздела.

VAR Е ELLIPSE ; VAR С CIRCLE ;

С := CIRCLE ( LENGTH ( 3.0 ), POINT ( 0.0, 0.0 ) ) ; E := С ;

Здесь тип MST(E) — CIRCLE, Предположим, что требуется получить радиус данной ок- ружности и присвоить его некоторой переменной L. Можно попытаться сделать это так.

VAR L LENGTH ;

L := THE_R ( Е ) ; /* Ошибка времени компиляции - недопустимый тип!!! */

Однако, как явствует из комментария, этот код во время компиляции выдает сообщение об ошибке о несоответствии типов. Точнее говоря, ошибка возникает из-за того, что опера- тор THE R ("радиус") в правой части оператора присвоения требует, чтобы его аргумент от- носился к типу CIRCLE, а объявленный тип его аргумента Е — ELLIPSE, а не CIRCLE.

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

Чтобы решить эту проблему, введем новый оператор, который неформально будем называть TREAT DOWN (трактовать). Теперь можно корректно обратиться к оператору THE R и получить радиус окружности.

L := THE_R { TREAT_DOWN_AS_CIRCLE ( Е ) ) ;

Выражение TREAT DOWN_AS_CIRCLE(E) задается для того, чтобы для переменной Е в данном случае был объявлен тип CIRCLE и проверка при компиляции завершилась ус- пешно. При этом во время выполнения произойдет следующее.

  • Если текущее значение переменной Е относится к типу CIRCLE, значит, выражение в целом корректно и возвращает радиус данной окружности. А точнее, обращение к оператору TREAT DOWN дает результат (скажем, Z) с объявленным типом DT(Z), равным CIRCLE, поскольку задано уточнение ..._AS_CIRCLE, текущим конкретным типом MST(Z), равным типу MST(E), который в нашем примере имеет значение CIRCLE, и текущим значением v(Z), равным v(E). В результате при вычислении выражения THE_R(Z) будет получен требуемый радиус (который может быть при- своен переменной L).

  • Однако если текущее значение переменной Е в действительности будет относиться к типу ELLIPSE, а не к типу CIRCLE, то обращение к оператору TREAT DOWN приве- дет к ошибке из-за несоответствия типа во время выполнения.

Общее назначение оператора TREAT DOWN — обеспечить, чтобы ошибки несоответ- ствия типов во время выполнения могли произойти только в рамках вызова оператора TREAT DOWN.

Замечание. Предположим, что тип CIRCLE, в свою очередь, имеет собственный под- тип (скажем, О CIRCLE, где О CIRCLE — окружность, центр которой расположен в на- чале координат).

TYPE 0_CIRCLE POSSREP ( R LENGTH ) SUBTYPE_0F ( CIRCLE )

CONSTRAINT ( THE CTR ( 0_CIRCLE ) = POINT ( 0.0, 0.0 ) ) ;

Тогда текущее значение переменной Е в данное время может иметь конкретный тип 0_CIRCLE, а не просто CIRCLE, Рассмотрим вызов оператора TREAT DOWN в этом случае.

TREAT_DOWN_AS_CIRCLE ( Е )

Его выполнение завершится нормально, и будет выдан результат (скажем, Z) с объяв- ленным типом DT(Z), равным CIRCLE, поскольку задано уточнение ... AS CIRCLE, теку- щим конкретным типом MST(Z), равным типу 0_CIRCLE, поскольку О CIRCLE — это кон- кретный тип переменной Е, и текущим значением v(Z), равным v(E). Короче говоря, оператор TREAT DOWN всегда возвращает конкретный тип; он никогда не "повышает" тип, чтобы сделать его менее конкретным, чем он был прежде.

Приведем более формальное изложение семантики вызова оператора TREAT_D0WN_AS_T (X), где X — некоторое скалярное выражение. Во-первых, тип Т должен быть подтипом типа DT(X) (это проверяется во время компиляции). Во-вторых, конкрет- ный тип MST (X) должен быть подтипом типа Т (это проверяется во время выполнения). Если данные условия удовлетворяются, то после обращения к оператору возвращается результат Z с типом DT(Z), равным Т, типом MSTJZ), равным типу MST(X), и текущим значением v (Z), равным v (X).

Замечание. В [3.3] также определяется общая форма оператора TREAT DOWN, которая допускает, чтобы один операнд "трактовался" как имеющий тип другого операнда вме- сто некоторого явно указанного конкретного типа.

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