Subtypes and Supertypes Setting the Scene

         

Persistence Not Orthogonal to Type


()

Третий манифест не соглашается с объектным миром

В сентябрьском выпуске я объяснял, почему я чувствую, что для концентрации объектного мира на инкапсуляции имеются недостаточные основания. В этом месяце я хочу обратить внимание на другое хорошо известное утверждение мира объектов -- а именно, то утверждение, что постоянство хранения (persistence) ортогонально типу (для краткости я буду ссылаться на это утверждение как на POTT). POTT означает, что (a) любая структура данных, которая может быть создана традиционной прикладной программой -- например, массив, ссылочный список или стек -- может быть сохранена как объект в объектной базе данных и (b) структура таких объектов является видимой для пользователя. Например, объект EX, который является коллекцией служащих данного отдела. EX может быть реализован как список или как массив, и пользователи будут должны знать, как это сделано на самом деле (поскольку соответственно будут различаться операции доступа).

Одной из наиболее ранних статей, если не в самой ранней, выражающей позицию POTT, была статья "Types and Persistence in Database Programming Languages" Малькольма Аткинсона (Malcolm Atkinson) и Питера Бьюнмана (Peter Buneman) (ACM Computing Surveys 19(2), June 1987). Аткинсон был также одним из авторов манифеста "The Object-Oriented Database System Manifesto" [1], предлагавшего набор возможностей, который, по мнению авторов, должна поддерживать СУБД, если она объявляется "объектно-ориентированной". Конечно, среди прочего было включено и свойство POTT.

В последствии манифест "The Third Generation Database System Manifesto" также провозглашал POTT как цель будущих систем баз данных ("Постояноство хранение X для различных X является хорошей идеей") [2]. И авторы The Object Database Standard: ODMG 2.0 также согласны с этим: "[Мы] определяем объектную СУБД как СУБД, интегрирующую возможности базы данных с возможностями объектно-ориентированного языка программирования". Объктная СУБД делает объекты базы данных такими как если бы они были объектами языка программирования… [она] расширяет язык прозрачно постоянно хранимыми данными… и другими возможностями базы данных". [3] (курсив К.Дейта).

Однако занятая мной и Хью Дарвеном позиция в Третьем Манифесте весьма отличается:

"Для постоянного хранения предназначены базы данных (и ничто другое)… [Поскольку] единственным видом переменной, которую мы допускаем в базе данных, является [реляционная переменная] или relvar, единственным видом переменной, которая может обладать свойством постоянного хранения, является relvar".



POTT нарушает независимость данных


Одна из причин, по которой мы отвергаем POTT, является то, что эта идея ведет к утрате независимости данных. Как я уже отмечал, POTT означает, что любая структура данных, которая может быть создана в традиционной прикладной программе, может быть сохранена как объект в объектной базе данных, и структура таких объектов является видимой для пользователя. Конечно, это подход "все годится" по отношению к тому, что может содержаться в базе данных, является основным отличием объектной модели от реляционной, поэтому рассмотрим его более пристально. Замечание: В целях обсуждения я предполагаю, что термин объектная модель является хорошо определенным и хорошо понимаемым, хотя такое предположение -- мягко говоря -- немного льстит объектам.

При принятии этого предположения мы можем охарактеризовать различия между этими двумя подходами следующим образом:

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

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

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

Одним из последствий этого положения дел, как уже говорилось -- в противоречии с традиционным здравым смыслом -- является то, что объектные системы могут поддерживать меньшую независимость данных, чем реляционные системы. Например, предположим, что в некоторой объектной базе данных реализация упоминавшегося ранее объекта EX (коллекции служащих данного отдела) изменяется из массива в список со ссылками.
Что произойдет с существующим программным кодом, обращающимся к объекту EX? Он сломается.

Возможно, мне следует задать еще один вопрос: Зачем нам нужно менять реализацию EX таким способом? Конечно, в основе ответа лежит эффективность. В идеале изменение не должно затрагивать ничего, кроме эффективности; однако на деле это не так.

На самом деле, мне кажется, что возможность наличия всех этих разных способов представления данных на логическом уровне является примером того, что я называю ложной общностью. Я бы даже сказал, что вся эта идея проистекает из неудачи четкого разделения модели и реализации (может понадобиться множество разных представлений на физическом уровне, но они не нужны на логическом уровне). Помню, как однажды сказал Э.Ф. Кодд (E.F. Codd) (в ответ на вопрос на панельной дискуссии): "Если вы скажете, что имеете в своей системе 50 разных способов представления данных [на логическом уровне, конечно], то я скажу вам, что 49 из них - лишние".




POTT порождает дополнительную сложность


Очевидно, что POTT действительно приводит к дополнительной сложности -- под "сложностью" я прежде всего понимаю сложность для пользователя, хотя порождаются большие сложности и для системы. Например, реляционная модель поддерживает только один "генератор типа коллекции" -- RELATION -- совместно с набором операций -- соединение, проекция и т.д. -- которые применимы ко всем "коллекциям" этого типа (другими словами, ко всем отношениям). В отличие от этого, в соответствии с предложениями ODMG поддерживаются четыре генератора типа коллекции - SET, BAG, LIST и ARRAY, для каждого из которых имеется набор операций, применимых ко всем коллекциям данного типа. И я утверждаю, что операции ODMG одновременно сложнее реляционных и обладают меньшей можностью. Вот, например, операции ODMG для списков:

IS_EMPTY IS_ORDERED ALLOWS_DUPLICATES CONTAINS_ELEMENT INSERT_ELEMENT REMOTE_ELEMENT CREATE_ITERATOR CREATE_BIDIRECTIONAL_ITERATOR REMOTE_ELEMENT_AT RETRIEVE_ELEMENT_AT REPLACE_ELEMENT_AT INSERT_ELEMENT_AFTER INSERT_ELEMENT_BEFORE INSERT_ELEMENT_FIRST INSERT_ELEMENT_LAST REMOVE_FIRST_ELEMENT REMOTE_LAST_ELEMENT RETRIEVE_FIRST_ELEMENT RETRIEVE_LAST_ELEMENT CONCAT APPEND

Между прочим, стоит заметить мимоходом, что ODMG не поддерживает генератор типа RELATION. Авторы The Object Database Standard: ODMG 2.0 утверждают, что "модель данных ODMG включает реляционную модель данных за счет наличия типа TABLE", но тип TABLE строго недостаточен во многих отношениях; в частности, отсутствуют многие из критических реляционных операций -- например, соединение. В связи с тем утверждением, что модель ODMG "включает реляционную модель" или "является более мощной", имеется много дополнительных проблем, но ограничения размера этой заметки не позволяют рассмотреть их здесь.

Далее, ODMG поддерживает язык запросов OQL - язык только для выборки (операции обновления отсутствуют), который в свободном стиле срисован с SQL. Более конкретно, OQL:

Обеспечивает средства составления запросов в стиле SQL - SELECT-FROM-WHERE для множеств, мультимножеств, списков и массивов Обеспечивает аналоги конструкций SQL GROUP BY, HAVING и ORDER BY Поддерживает объединение, пересечение и вычитание, а также специальные операции для списков и массивов (например, "получить первый элемент") Поддерживает "выражения пути" (path expressions) для прохода по связям между объектами.


В The Object Dtabase Standard: ODMG 2.0 содержится ряд утверждений про OQL. Вот пара из них (в обоих случаях курсив добавлен К. Дейтом):

"Там, где это возможно, в качестве основы OQL мы используем реляционный стандарт SQL, хотя OQL поддерживает более мощные возможности." "OQL обладает большей мощностью [чем реляционный язык запросов]."

С моей точки зрения, наоборот, OQL очень хорошо иллюстрирует то, что POTT приводит к дополнительной сложности! То есть я утверждаю, что OQL более сложен, а не более мощен (как кажется, люди из мира компьютеров часто путают эти два понятия). И дополнительная сложность порождается тем фактом, что пользователю демонстрируется так много разных структур данных. Мне кажется, что это положение дел является прямым следствием непризнания преимуществ строго разделения модели и реализации.

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

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


Каталог должен описывать переменные списков наряду с переменными отношений. (А что будет собой представлять сам каталог? Переменные списков? Переменные отношений? Смесь того и другого?) Нам нужна теория проектирования списков, аналогичная существующей реляционной теории проектирования. Нам также потребуются руководства относительно того, когда следует использовать переменные списков, а когда переменные отношений. И так далее, поскольку я уверен, что этот перечень не является исчерпывающим.

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

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

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


В своей книге The Relational Model for Database Management Version 2 (Addison-Wesley, 1997) Кодд приводит ряд аргументов в пользу поддержки этого принципа (аргументов, с которыми я, конечно, согласен). Как мы утверждаем в Третьем Манифесте, отношения являются необходимыми и достаточными для представления любых данных (на логическом уровне). Другими словами, мы должны иметь отношения и нам не требуется ничего, кроме отношений.

Так откуда же возникла идея POTT? Мне кажется, здесь мы имеем (как это часто случается) фундаментальную путаницу в понятиях модели и реализации. Более точно, было замечено, что некоторые SQL-продукты не слишком хорошо выполняют некоторые операции (в особенности соединения); на основе этого было сделано предположение, что эффективность была бы улучшена, если бы мы могли использовать, скажем, списки или массивы вместо отношений. Но в этой идее содержится серьезна путаница; в ней смешиваются логический и физический уровни. Никто не утвержает, что, например, списки не могли бы быть полезны на физическом уровне; вопрос в том, должны ли списки и тому подобное демонстрироваться на логическом уровне. И очень строгой позицией защитников реляционного подхода и, частности, авторов Третьего Манифеста является то, что ответом на этот вопрос является "нет".


А почему не операции 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_операций, могут быть вложенными.



Decent Exposure


()

Краткий взгляд на некоторые фундаментальные идеи Третьего Манифеста

В своей августовской заметке я упоминал, что в Третьем Манифесте -- Манифесте для краткости -- для любого заданного скалярного типа требуется определение некоторых "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 ) ) ;

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



Замечание по поводу синтаксиса


Последнее замечание: Простой способ поддержки нашего требования наличия 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_нотацию. (Одна из причин в том, что в нашей книги про Манифест мы уже использовали точечную нотацию для другой цели, не относящейся напрямую к теме заметки этого месяца, и я хочу по мере возможности оставаться в согласии с этой книгой.)



Что имеется в виду, когда мы говорим "тип"?


Выражаясь нестрого, тип - это именованное множество значений (т.е. все возможные значения данного типа) вместе с ассоциированным набором операций, которые можно применять к этим значениям. Например, тип SMALLINT мог бы состоять из всех целых чисел в диапазоне от -32,768 до +32,767, а ассоциированным набором операций могли бы быть "+", "-", "*", "=", "

Данный тип может быть определенным либо системой, либо пользователем. Часть определения любого типа представляет собой спецификацию всех возможных значений этого типа. Такие значения могут быть произвольно сложными. Реальное или физическое представление таких значений всегда скрывается от пользователя. Такими значениями можно оперировать только посредством операций, определенных для данного типа. В состав этих операций входит операция selector, которая позволяет "выбрать" -- или специфицировать -- произвольное значение данного типа (посредством соответствующего вызова селектора), и операция equality, которая дает возможность проверить, являются ли два значения данного типа на самом деле одним значением. Некоторые типы являются подтипами других супертипов. Если B - подтип A, то все операции и ограничения, применимые к A, применимы и к B (наследование); однако B может иметь свои собственные операции и ограничения, не применимые к A.

Заметим, что в реляционном мире типы обычно называются "доменами"; в объектном мире они обычно называются "классами". В этой серии я буду использовать термин "тип" (большей частью).



Литература


Taivalsaari, Andrew. "On the Notion of Inheritance", ACM Comp. Surv. 28, No. 3. September 1996. Atkinson, Malkolm et al. "The Object-Oriented Database System Manifesto", Proceedings: First International Conference on Deductive and Object-Oriented Databases, Kyoto, Japan, 1989. Elsevier Science, 1990. J. Craig Cleaveland. An Introduction to Data Types. Addison-Wesley, 1986. Meyer, Bertrand. "The Many Faces of Inheritance: A Taxonomy of Taxonomy", IEEE Computer 29, No. 5. May 1996. Baclavski, Kenneth and Bipin Indurhya. Technical Correspondence, CACM 37, No. 9. September, 1994. International Organization for Standardization. Database Language SQL - Part 2: SQL/Foundation (Committee Draft). SQL3 Committee Drafts can be found on the World Wide Web at ftp://jarry.ece.umassd.edu/isowg3/dbl/BASEdocs/public. Date, C.J. and Hugh Darven. Foundation for Object/Relational Databases: The Third Manifesto. Addison-Wesley, 1998.



Почему наследование типов?


Вероятно, мне следует начать с обсуждения вопроса "почему эта тема заслуживает первоочередного внимания?". На этот вопрос возможны по крайней мере два ответа:

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

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

Однако, несмотря на потенциальные преимущества, все еще ощущается небольшое согласие относительно формальной, строгой и абстрактной модели наследования типов -- хотя существуют языки и продукты, в которых поддерживается некоторая разновидность наследования типов (и уже в течение некоторого времени) и в течение многих лет в книгах, статьях и презентациях обсуждается наследование типов. Цитируя Эндрю Тайвалсаари (Andrew Taivalsaari), "Основная идея наследования весьма проста … [несмотря на] ее центральную роль в современных … системах, наследование все еще представляет собой противоречивый механизм … [Какое-либо] исчерпывающее представление о наследовании все еще отсутствует."

Вот еще несколько цитат, иллюстрирующих тот же самый общий аспект:

Малькольм Аткинсон (Malcolm Atkinson) и др.: "Имеется по крайней мере четыре типа наследования: наследование подстановкой, наследование включением, наследование ограничением и наследование специализацией…В разной степени эти четыре типа наследования обеспечиваются в существующих системах и прототипах, и мы не предписываем конкретный стиль наследования." [2] Дж.
Крейг Кливленд говорит, что "[наследование может] основываться на [множестве] различных критериев, и отсутствует общепринятое стандартное определение" [3], и он переходит к изложению восьми различных интерпретаций. (Бертран Мейер [Bertrand Meyer] приводит 12 интерпретаций [4].) Кеннет Баклавски (Kennet Baclavski) и Бипин Индуркхиа (Bipin Indurkhia) говорят: "Язык программирования [всего лишь] обеспечивает набор механизмов [наследования]. Хотя эти механизмы ограничивают возможные действия в этом языке и то, какие представления о наследовании могут быть реализованы … они сами по себе не узаконивают то или иное представление о наследовании. Классы, специализации, обобщения и наследования - это всего лишь концепции и … они не обладают универсальным объективным смыслом … Из этого [факта] следует, что способ внедрения наследования в конкретную систему возлагается на разработчиков [этой] системы, и это является политическим решением, реализуемым на основе доступных механизмов." [5] Другими словами, просто отсутствует модель!

Другие люди предлагают модели наследования, которые обладают противоречивыми, неинтуитивными и другими нежелательными свойствами. Например, текущие предложения SQL3 допускают такие вещи, как "неквадратные квадраты" [6] (т.е. значения типа SQUARE, стороны которого имеют разную длину) -- с тем результатом, что SQL3 трудно считать "хорошей моделью реальности". В действительности SQL3 даже не дает возможности устанавливать ограничения типа (вроде того, что у значений типа SQUARE стороны должны иметь одинаковую длину), не позволяет поддерживать их без посторонней помощи -- это, безусловно, неудовлетворительное положение дел. Что еще хуже, SQL3 не дает возможности устанавливать и поддерживать такие ограничения даже при отсутствии поддержки наследования, вероятно, при том предположении, что такая поддержка появится когда-то в будущем.

Однако в нашей книги The Third Manifesto мы с Хью Дарвеном предлагаем модель наследования, которая, как мы полагаем, является хорошей "моделью реальности" и не страдает такими недостатками [7].Конечно, данная серия основывается на этой модели; по существу, она представляет собой легкое введение (не слишком глубокое) в наиболее важные идеи этой модели. Поэтому позвольте мне немедленно со стыдом сознаться, что одной из моих целей является реклама. Мы хотели бы, чтобы большая индустрия обратила серьезное внимание на наши идеи, потому что мы полагаем, что они могли бы служить основанием общепринятой модели, отсутствие которой так остро ощущается в настоящее время.

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


Подтаблицы и супертаблицы


Теперь должно быть понятно, что наша модель наследования имеет отношение к тому, что реляционных терминах можно было бы назвать наследованием доменов. Однако, рассматривая возможность наследования в реляционном контексте, многие люди (вероятно, большинство) немедленно приходят к заключению, что обсуждается некоторая форма наследования таблиц. Например, в текущих предложениях SQL3 содержится поддержка того, что иногда называют "подтаблицами" и "супертаблицами", когда некоторая таблица B может наследовать все столбцы некоторой другой таблицы A с добавлением некоторых собственных столбцов (см. рис.1). Однако наша позиция состоит в том, что идея "подтаблиц и супертаблиц" относится к совершенно другому явлению, возможно, интересному (хотя мы относимся к нему немного скептически). Но наша позиция состоит в том, что эта идея не имеет ничего общего с наследованием типов как таковым.

Рис. 1. Пример подтаблицы/супертаблицы

И еще одно предварительное замечание. Предмет наследования типов в большой степени связан с данными в целом, но не так сильно касается постоянно хранимых (persistent) данных или данных баз данных (т.е. в идее наследования содержится в лучшем случае лишь небольшая часть того, что применимо только к постоянно хранимым данных). Поэтому для простоты все мои примеры в этой серии будут выражаться в терминах локальных данных, а не данных базы данных. (Поддержка наследования действительно имеет определенное влияние на реляционные данные и реляционные операции, но даже это влияние относится только к локальным отношениям вне их связи с базой данных.)

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



Предварительные замечания


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

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



Простое и множественное наследование


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



Скаляры, кортежи и отношения


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



Структурное и поведенческое наследование


Напомним, что скалярные значения могут иметь внутреннюю (физическую) структуру или представление произвольной сложности; например, как мы уже знаем, эллипсы и окружности при соответствующих обстоятельствах могут законным образом рассматриваться как скалярные значения, хотя их внутренняя структура может быть достаточно сложной. Однако эта внутренняя структура всегда скрыта от пользователя. Следовательно, что при обсуждении наследования (по крайней мере, в соответствии с нашей моделью) мы не имеем в виду наследование структуры, поскольку с точки зрения пользователя нет никакой структуры для наследования! Другими словами, нас интересует то, что иногда называют поведенческим наследованием, а не структурное наследование (где под "поведением" понимаются операции -- хотя напоминаю, что наследуются и ограничения). Заметьте, что мы не устраняем структурное наследование; мы всего лишь относимся к нему как к реализационному вопросу, который не существенен для модели.



Типизированы и все переменные.


Каждая переменная объявляется как переменная в точности одного типа. В языке Tutorial D, например, мы могли бы объявлять переменные следующим образом:

VAR E ELLIPSE ;

Здесь объявленным типом переменной E является ELLIPSE. При отсутствии наследования все возможные значения данной переменной относятся в точности к одному типу, а именно, к соответствующему объявленному типу. Однако при наличии наследования данная переменная может иметь значение, которое одновременно относится к нескольким типам; например, текущее значение переменной E может быть эллипсом, который на самом деле является окружностью, и, следовательно, в одно и то же время относится и к типу ELLIPSE, и к типу CIRCLE.



Все значения типизированы


Можно представлять, что с каждым значением связано что-то вроде флага, на котором написано "я целое", "я номер служащего", "я точка", "я эллипс" или что-то еще. При отсутствии наследования у каждого значения в точности один тип. Однако при наличии наследования значение может одновременно относиться к нескольким типам; например, данное значение в одно и то же время может относиться к типам CIRCLE и ELLIPSE.



Закладывая основу нашего исследования наследования типов


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

Каждая окружность является эллипсом, но обратное неверно. (Некоторые эллипсы не являются окружностями.) Следовательно, любая операция, применимая к эллипсам, вообще говоря, применима и к окружностям (поскольку окружности являются эллипсами), но обратное неверно. Например, операция THE_CTR (центр) применима к эллипсам и тем самым к окружностям, но операция THE_R (радиус) применима только к окружностям. Более того, ограничение типа, применимое к эллипсам, вообще говоря, применимо и к окружностям (снова потому, что окружности являются эллипсами), но обратное неверно. Например, если к эллипсу применимо ограничение a >= b (где a и b - большая и меньшая полуоси эллипса соответственно), то окружности должны удовлетворять тому же ограничению. Конечно, для окружностей a и b совпадают с радиусом r, и это ограничение удовлетворяется тривиально; в действительности, к окружностям, но не ко всем эллипсам, в точности применимо ограничение a = b.

Таким образом, тип CIRCLE наследует операции и ограничения от типа ELLIPSE (свободно выражаясь), но имеет также собственные операции и ограничения, не применимые к типу ELLIPSE. Замечание: Здесь и во всей этой серии я буду использовать неуточненный термин "ограничение" в конкретном смысле ограничения типа, а в противном случае явно оговаривать другой смысл. Чтобы напомнить, ограничение типа (называемое также ограничением домена) - специфицирует просто допустимые значения данного типа.

Кстати, в этом месте новички иногда впадают в заблуждение: а именно, подтип обладает подмножеством значений, но супермножеством свойств. (Я использую термин "свойства" в качестве удобного сокращения для "операций и ограничений".) Например, тип CIRCLE содержит подмножество значений типа ELLIPSE, но индивидуальная окружность обладает всеми свойствами эллипса, а также и другими.