1999 г
Уважаемые читатели!
Одним из достоинств книги Криса Дейта и Хью Дарвена Foundation for Object/Relational Databases: The Third Manifesto (Addison-Wesley, 1998) является очень аккуратное отношение к типам данных, значениям и переменным. Еще раз замечу, что лучше прочитать саму книгу, но и заметки Дейта позволяют получить хорошее представление о подходе Третьего Манифеста. Как всегда у Дейта мне нравится сочетание точности изложения с простым и легко читаемым текстом. Будем ждать следующих заметок.
Приятного чтения, Сергей Кузнецов
Intelligent Enterprise's
Database Programming & Design OnLine
November 1998
According to Date
C.J.Date
Decent Exposure
(
http://www.dbpd.com/vault/9811/date.shtml)
Краткий взгляд на некоторые фундаментальные идеи Третьего Манифеста
В своей августовской заметке я упоминал, что в Третьем Манифесте -- Манифесте для краткости -- для любого заданного скалярного типа требуется определение некоторых "THE_операций", демонстрирующих некоторое возможное представление значений и переменных этого типа. Я приводил пример типа POINT, для которого мы могли бы определить операции "THE_X" и "THE_Y", раскрывающие возможное представление в Декартовых координатах. Однако я подчеркивал также тот факт, что Декартовы координаты являются только возможным представлением -- действительное представление могло бы опираться на Декартовы координаты, полярные координаты или на что-либо еще. Возможные представления связаны с моделью; в отличие от этого действительные представления связаны только с реализацией.
Эти "возможные представления" составляют фундаментальную часть размышлений, лежащих в основе Манифеста. В частности, они тесно связаны с вопросом, который я достаточно подробно собираюсь обсудить в следующих месяцах -- а именно (удивительно сложный) вопрос наследования типов. Поэтому в этом месяце я хотел бы заложить некоторую основу, разъяснив природу возможных представлений на немного более глубоком уровне. Начнем с обсуждения понятия селекторных операций (selector operators).
Селекторные операции
Для каждого типа данных (в частности, для любого скалярного типа) в Манифесте требуется, помимо прочего, определение операции, назначение которой состоит просто в "выборке" конкретного значения данного типа. Такие селекторные операции являются обобщением знакомого понятия литерала. (Литерал - это специальный случай вызова селектора, но не все вызовы селектора являются литералами.) Например, рассмотрим следующий фрагмент кода:
VAR X RATIONAL INIT ( +4.0 ) ;
VAR Y RATIONAL INIT ( -3.0 ) ;
VAR P POINT ;
P : = POINT ( X, Y ) ;
(Пример выражен на языке Tutorial D; напомним, что три месяца тому назад я говорил, что Tutorial D - это язык, определенный главным образом как средство для иллюстрации и обсуждения особенностей Манифеста. Я должен также пояснить, что везде в Манифесте мы используем более точный термин RETIONAL вместо традиционного REAL; в конце концов, числа с плавающей точкой по определению являются рациональными числами, а не действительными числами общего вида.)
Действие приведенного фрагмента кода состоит в присвоении переменной P конкретного значения POINT -- а именно, точки с Декартовыми координатами (4.0, -3.0). Выражение в правой части операции присваивания в точности представляет собой вызов селектора типа POINT; эффект вызова в точности состоит в выборе точки с указанными Декартовыми координатами. (Замечание: Если бы я написал просто POINT (4.0, -3.0) вместо POINT (X, Y), то использовал бы вызов селектора, представляющий в действительности литерал. Кроме того, для читателей, знакомых с объектными системами, я должен подчеркнуть тот факт, что в нашей модели переменная P теперь в действительности содержит точку как таковую, а не "ссылку на" точку или "объектный ID" точки. В нашей модели явно отвергаются объектные ID.)
Следовательно, заметим, что параметры данного селектора S составляют -- обязательным образом -- возможное представление PR объектов соответствующего типа T. В приведенном примере Декартовы координаты X и Y составляют возможное представление точек.
В действительности в объектных системах имеются некоторые аналоги наших селекторных операций (более обычным объектным термином является функции- конструкторы), и поэтому пользователи таких систем необходимым образом знакомы с некоторыми возможными представлениями. Однако в объектных системах, вообще говоря, не требуется, чтобы эти возможные представления раскрывались для произвольных целей. Например, пользователи могут знать на основе формата соответствующей функции-конструктора, что для точек существует возможное представление на основе Декартовых координат, но если система не обеспечивает операций для "взятия" координат X и Y для любой заданной точки, эти пользователи не смогут выполнять все разновидности простых операций. Развивая приведенный фрагмент кода, если не существует операция "get Y", то пользователь не сможет узнать, какова координата Y точки P, даже если он или она знают, что значение этой координаты есть -3.0! Другими словами, кажется, что объектная ориентация допускает не слишком разумную политику разработки.
В свете приведенных наблюдений мы решили в своем Манифесте настаивать на некоторой подходящей дисциплине. Более конкретно, мы настаиваем на следующем:
- Определение скалярного типа должно включать спецификацию по меньшей мере одного возможного представления значений этого типа.
- Спецификация возможного представления вызывает "автоматическое" определение соответствующей селекторной операции. Поэтому заметим, что для любого возможного представления имеется соответствующий селектор (и в Tutorial D мы применяем очевидное соглашение, что любое данное возможное представление и соответствующий ему селектор имеют одно и то же имя, что подчеркивает их связь вида "рука в перчатке").
- Спецификация возможного представления также вызывает "автоматическое" определение набора операций, раскрывающих это возможное представление для произвольных целей.
Вот пример (опять Tutorial D):
TYPE POINT
POSSREP POINT ( X RATIONAL, Y RATIONAL )
POSSREP POLAR ( X RATIONAL, Y RATIONAL ) ;
Этот оператор определяет уже использовавшийся в предыдущих примерах тип POINT. У типа POINT имеются два возможных представления, называемых POINT (Декартовы координаты) и POLAR (полярные координаты) соответственного, и два соответствующих селектора с такими же именами. (В Tutorial D мы используем еще одно соглашение, в соответствии с которым возможное представление без собственного явно заданного имени по умолчанию наследует имя соответствующего типа; таким образом, в первой из двух спецификаций POSSREP в приведенном примере явное имя POINT можно было бы опустить.)
Конечно, в Манифесте требуется, чтобы типы кортежей и отношений также обладали селекторами. Однако для простоты здесь я буду концентрироваться на скалярных типах.
THE_операции
Как я уже отмечал, в Манифесте также требуется, чтобы для каждого возможного представления данного скалярного типа был "автоматически" определен набор операций, целью которых является раскрытие этого возможного представления. И для этого конкретно предлагается использовать THE_операции. Вот подходящая выдержка из Манифеста (несколько отредактированная):
Пусть PR - это возможное представление скалярного типа T, и пусть у PR имеются компоненты C1, C2, …, Cn. Определим THE_C1, THE_C2, …, THE_Cn как семейство операций таких, что для каждого i (i = 1, 2, …, n) операция THE_Ci обладает следующими свойствами:
- Ее единственный параметр принадлежит к объявленному типу T.
- Если вызов операции появляется в позиции "источника" (в частности, в правой части операции присваивания), то он возвращает компонент Ci аргумента. (Более точно, возвращается значение компонента Ci возможного представления PR(v) значения аргумента v.)
- Если вызов операции появляется в позиции "цели" (в частности, в левой части операции присваивания), то, во-первых, этот аргумент должен быть явно специфицирован как скалярная переменная, а не как произвольное скалярное выражение; во-вторых, вызов действует как псевдопеременная в том смысле, что в действительности указывает на компонент Ci аргумента -- а не только возвращает его значение. (Более точно, он указывает на компонент Ci возможного представления PR(V) своего аргумента-переменной V.)
Замечание: Термин псевдопеременная взят из PL/1. Однако помните, что псевдопеременные в PL/1 не могут быть вложенными, а THE_псевдопеременные - могут. Другими словами, мы действительно относимся к вызовам псевдопеременных как к ссылкам на переменные, из чего следует, помимо прочего, что они могут появляться в качестве аргументов других таких вызовов.
Вот пример:
TYPE TEMPERATURE POSSREP CELSIUS ( C RATIONAL ) ;
VAR TEMP TEMPERATURE ;
VAR CEL RATIONAL ;
CEL : = THE_C ( TEMP ) ;
THE_C ( TEMP ) : = CEL ;
В первом из этих присваиваний переменной CEL типа RATONAL присваивавется температура, соответствующая текущему значению переменной TEMP типа TEMPERATURE, преобразованная при необходимости к градусам Цельсия; во втором присваивании текущее значение переменной CEL типа RATIONAL, рассматриваемое как температура в градусах Цельсия, используется для обновления переменной TEMP типа TEMPERATURE. Таким образом, операция THE_C раскрывает возможное представление "градусы Цельсия" температур (для целей и чтения, и обновления). Однако это возможное представление не обязательно является действительным представлением. Например, температуры могли бы реально представляться в градусах Ференгейта, а не Цельсия.
Вот слегка более сложный пример с использованием ранее определенного типа POINT:
VAR Z RATIONAL ;
VAR P POINT ;
Z : = THE_X ( P ) ;
THE_X ( P ) : = Z ;
В первом из этих присваиваний переменной Z типа RATIONAL присваивается координата X точки, соответствующей текущему значению переменной P типа POINT; во втором присваивании текущее значение переменной Z типа RATIONAL используется для обновления координаты X переменной P типа POINT (говоря немного вольно). Следовательно, как отмечалось ранее, операции THE_X и THE_Y раскрывают возможное представление точек на основе Декартовых координат для целей и чтения, и обновления; однако снова это возможное представление не обязательно то же, что и какое-либо действительное представление.
И еще один пример, основанный на предыдущем (здесь LINESEG обозначает отрезок прямой):
TYPE LINESEG POSSREP ( BEGIN POINT, END POINT ) ;
/* начальная и конечная точки -- соответствующий */
/* селектор по умолчанию называется LINESEG */
VAR Z RATIONAL ;
VAR LS LINESEG ;
Z : = THE_X ( THE_BEGIN ( LS ) ) ;
THE_X ( THE_BEGIN ( LS ) ) : = Z ;
В первом из этих присваиваний переменной Z присваивается координата X точки начала текущего значения LS. Во втором присваивании текущее значение Z используется для обновления координаты X точки начала переменной LS. (Обратите внимание на вложенность псевдопеременных в этом втором присваивании.) Таким образом, операции THE_BEGIN и THE_END раскрывают возможное представление отрезков на основе "точек начала и конца" -- снова для целей и чтения, и обновления. Однако снова это возможное представление не обязательно совпадает с каким-либо реальным представлением.
Кстати, в Манифесте требуется также поддержка множественной формы присваивания. Так, например, можно использовать оператор
THE_BEGIN ( LS ) : = P ,
THE_END ( LS ) : = Q ;
для обновления точек начала и конца переменной отрезка LS в одной операции.
THE_псевдопеременные - это всего лишь сокращенная форма
Теперь заметим, что THE_псевдопеременные не являются логически необходимыми! Рассмотрим "обновляющее" присваивание из первого из трех примеров, приведенных в предыдущем разделе:
THE_C ( TEMP ) : = CEL ;
Это присваивание, в котором используется псевдопеременная, логически эквивалентно следующем, где псевдопеременная не используется:
TEMP : = CELSIUS ( CEL ) ; /* вызывается селектор CELSIUS */
Аналогично, обновляющее присваивание второго примера было следующим:
THE_X ( P ) : = Z ;
Вот логический эквивалент без использования псевдопеременной:
P : = POINT ( Z, THE_Y ( P ) ) ; /* вызывается селектор POINT */
Третий пример:
THE_X ( THE_BEGIN ( LS ) ) : = Z ;
Логический эквивалент:
LS : = LINESEG /* вызывается селектор LINESEG */
( POINT ( Z, THE_Y ( THE_BEGIN ( LS ) ), THE_END ( LS ) ) ;
Другими словами, псевдопеременные сами по себе не требуются строго, чтобы поддерживать обсуждаемый вид покомпонентных обновлений (где под "компонентом" я, конечно, понимаю компонент возможного представления). Однако подход всевдопеременных кажется более интуитивно привлекательным, чем его альтернатива (для которой псевдопеременные можно считать сокращенной записью); более того, этот подход потенциально даже более привлекателен -- хотя по-прежнему не необходим логически) -- если поддерживается наследование типов, что мы увидем в следующих выпусках. Он также обеспечивает более высокую степень невосприимчивости к изменению синтаксиса соответствующего селектора. И такой подход может более эффективно работать -- хотя, конечно, эффективности не имеет ничего общего с моделью.
А почему не операции GET_ и SET_?
Как вы, должно быть, знаете, в подобных контекстах более принято говорить не в терминах THE_операций, как это делается в Манифесте, а в терминах операций GET_ и SET_. Например,
Z : = GET_X ( P ) ;
/* получить в Z координату X точки P */
CALL SET_X ( P, Z ) ;
/* установить в координату X точки P значение Z */
GET_ и SET_ - это примеры того, что в Манифесте называется операциями только чтения и обновления соответственно.
Почему же мы предпочитаем свои THE_операции более традиционным операциям GET_ и SET_? Ответ опирается на тот факт, что SET_операции являются обновляющими операциями, а в нашей нашей модели обновляющие операции не возвращают значений. Мы навязываем это правило, потому что не хотим допустить существования только читающих выражений, производящих побочные эффекты; в частности, мы не хотим допустить существования "простых выборок", обладающих побочным эффектом обновления базы данных! Однако из этого правила следует, что вызовы обновляющих операций не могут рассматриваться как скалярные выражения (поскольку у них нет значений) и, следовательно, они не могут вкладываться; вместо этого их необходимо считать операторами -- типично, CALL-операторами, как в примере.
Из того, что SET_операции не могут вкладываться, аналог с SET_операцией (например) для присваивания
THE_X ( THE_BEGIN ( LS ) ) : = Z ;
выглядел бы подобно следующему:
VAR TP POINT ;
TP : = GET_BEGIN ( LS ) ; /* делается копия точки начала */
CALL SET_X ( TP, Z ) ; /* эта копия соответствующим образом обновляется */
CALL SET_BEGIN ( LS, TP ) ; /* теперь обновляется точка начала */
Этот пример показывает, почему мы предпочитаем THE_псевдопеременные SET_операциям. Для симметрии мы предпочитаем THE_операции GET_операциям (хотя здесь стоило бы говорить о число синтаксических вопросах, а не о логическом отличии, поскольку GET_операции, в отличие от SET_операций, могут быть вложенными.
Замечание по поводу синтаксиса
Последнее замечание: Простой способ поддержки нашего требования наличия THE_операций мог бы основываться на использовании некоторого рода точечного синтаксиса уточнений. Вот несколько примеров (пересмотренные варианты ранее приведенных примеров):
Z : = LS.BEGIN.X ;
LS.BEGIN.X : = Z ;
LS : = LINESEG ( POINT ( Z, LS.BEGIN.Y ), LS.END ) ;
LS.BEGIN : = P ;
LS.END : = Q ;
Однако в этой серии я буду использовать THE_нотацию. (Одна из причин в том, что в нашей книги про Манифест мы уже использовали точечную нотацию для другой цели, не относящейся напрямую к теме заметки этого месяца, и я хочу по мере возможности оставаться в согласии с этой книгой.)