Modula2 vs Oberon (798529), страница 3
Текст из файла (страница 3)
Это зависит от способности определять фактический тип данного узла, что достигаетсяпробой типа (type test), булевским выражением видаt IS T’p IS P’Если при взятии пробы получается утвердительный результат, то должно допускатьсяприсваивание t’ := t и p’ := p, где t’ типа T’, а p’ типа P’. Однако, статический взгляд типовзапрещает это. Заметим, что оба присваивания нарушают правило совместимости поприсваиванию. Требуемое присваивание становится возможным за счет использованияохранников типов (type guard) в виде:t’ := t(T’)p’ := p(P’)а доступ к полю «z» типа T0 (см. предыдущие примеры) достигается за счет использованияохранника типа в адресате (designator) «t(T0).z».
Здесь охранник гарантирует, что «t» содержит(в настоящий момент) значение типа T0. По аналогии с проверками границ массивов иселекторами оператора CASE, ошибка в охраннике приводит к аварийному завершениюпрограммы.Если охранник вида t(T) гарантирует, что «t» имеет тип T, только в случае адресата, которыйначинается с «t», то локальный охранник (regional type guard) обеспечивают такую гарантию врамках целой последовательности операторов. Он имеет вид:WITH t: T DO <последовательность_операторов> ENDи специфицирует, что «t» должен рассматриваться как имеющий тип T на всейпоследовательности операторов. Как правило, T является расширением описанного типа объекта«t».
Заметим, что присваивание объекту «t» внутри данной области требует, чтобыприсваиваемое значение имело тип T (или его расширение). Локальный охранник служит дляуменьшения числа вычислений для обычных охранников.В качестве примера использования пробы типа и охранников рассмотрим следующие типы Node иObject, заданные в модуле «M»:TYPE Node = POINTER TO Object;Object = RECORD key, x, y: INTEGER;left, right: NodeEND;Элементы в структуре дерева опираются на переменную, называемую «root» (типа Node), апоиск их осуществляется через процедуру «element», определенную в модуле «M».PROCEDURE element (k: INTEGER): Node;VAR p: Node;BEGIN p := root;WHILE (p # NIL) & (p.key # k) DOIF p.key < k THEN p := p.left ELSE p := p.right ENDМИР ПК – ДИСК.
2003. № 8. СТУДИЯ ПРОГРАММИРОВАНИЯ6/12ИСКУССТВО ПРОГРАММИРОВАНИЯАВГУСТ 2003END;RETURN pEND element;И пусть расширения типа Object будут определены (вместе с соответствующими ссылочнымитипами) в модуле M1, который является клиентом модуля M:TYPERectangle = POINTER TO RectObject;RectObject = RECORD (Object) w, h: REAL END;Circle = POINTER TO CircleObject;CircleObject = RECORD (Object) rad: REAL; shaded: BOOLEAN END;После проведения поиска элемента, для выбора между различными расширениями используетсяпроба типа, а для доступа к полям расширения — охранник типа. Пример:p := M.element(k);IF p # NIL THENIF p IS Rectangle THEN ... p(Rectangle).w ...ELSIF (p IS Circle) & ~p(Circle).shaded THEN ... p(Circle).rad ...ELSIF ...Расширение системы базируется на той предпосылке, что новые модули, определяющие новыерасширения, могут быть добавлены без какой бы то ни было адаптации или дажеперекомпиляции уже существующих частей, хотя компоненты новых типов включаются в ужесуществующие структуры данных.Механизм расширения типа не только заменяет вариантные записи языка Modula-2, но ипредставляет альтернативный путь для обеспечения надежности типов.
Столь же важна и тароль, которую играют связанные типы в общей иерархии типов. Сравним, к примеру, типыModulaT0' = RECORD t: T; z: REAL END;T1' = RECORD t: T; w: LONGREAL END;которые связаны с описанием упомянутого ранее типа T, с приведенными выше расширеннымитипами Oberon — T0 и T1. Во-первых, типы языка Oberon воздерживаются от введения новойобласти именования.
Считая, что переменная «r0» имеет тип T0, мы пишем «r0.x», а не «r0.t.x»как в случае Modula. Во-вторых, типы T, T0' и T1' различны и друг с другом не связаны. Тогда кактипы T0 и T1 связаны с типом T на правах расширения. Это проявляется при взятии пробы типа,которая гарантирует, что переменная «r0» принадлежит не только типу T0, но и базовому типу T.Описание расширенных записей, проба типа и охранник типа — это единственныедополнительные средства, введенные в данном контексте. Более всестороннее обсуждениеданного вопроса можно найти в работе [3]. Концепция эта весьма схожа с понятием класса,используемым в языках Simula-67 [4], Smalltalk [5], Object Pascal [6], C++ [7] и других, гдеговорят, что свойства базового класса наследуются производными классами. Механизм классовпредусматривает, что все процедуры, применимые к объектам класса, будут определены вместе сописанием данных. Эта догма проистекает из понятия абстрактного типа данных, но она служитсерьезным препятствием при разработке больших систем, где крайне желательна возможностьдобавить новые процедуры, определенные в дополнительных модулях.
Очень неудобнообязательно переопределять класс только лишь потому, что какой-то метод (процедура) долженбыть добавлен или изменен, в особенности когда это изменение требует перекомпиляцииописания класса и всех его клиентных модулей.Мы особо подчеркиваем, что механизм расширения типа также применим и к статическиописанным объектам, используемым в качестве параметров. (Хотя, как показано в приведенномвыше примере, его ведущая роль возрастает при построении разнородных динамическихструктур данных.) Под такие объекты отводится память в рабочем пространстве, организованномв виде стека записей об активации процедур, и потому здесь используется преимущество крайнеэффективной схемы выделения и утилизации памяти.МИР ПК – ДИСК. 2003. № 8.
СТУДИЯ ПРОГРАММИРОВАНИЯ7/12ИСКУССТВО ПРОГРАММИРОВАНИЯАВГУСТ 2003В Oberon с объектами в тексте программы связаны скорее процедурные типы, нежели процедуры(методы). Подключение (binding) фактических методов (специфических процедур) к объектам(экземплярам) откладывается до того, как программа начнет исполняться. Ассоциацияпроцедурного типа с типом данных возникает через описание поля записи.
Этому полюназначается процедурный тип. Ассоциация метода (если пользоваться терминологией языкаSmalltalk) возникает через присваивание специфической процедуры как некоего значенияконкретному полю, а не через статическое объявление в описании расширенного типа, которыйзатем «переопределяет» объявления, фигурирующие в базовом типе. Такая процедураназывается «обработчиком» (handler). С использованием пробы типа обработчик способенразбираться в различных расширениях базового типа для записи (объекта). В Smalltalk правиласовместимости между классом и его подклассами ограничиваются указателями и, тем самым,нежелательным образом смешивают концепции метода доступа и типа данных.
В Oberonотношение между типом и его расширениями опирается на уже устоявшееся математическоепонятие проекции.В Modula имеется возможность определять ссылочный тип внутри исполнительного модуля ипроэкспортировать его в качестве скрытого типа путем использования того же самогоидентификатора в соответствующем описательном модуле. Конечный эффект выражается в том,что тип экспортируется, тогда как все его свойства остаются скрытыми (не видимыми клиентам).В Oberon этот механизм обобщается в том смысле, что выделение экспортируемых полей записисовершенно произвольно и включает те случаи, когда экспортируются сразу все поля и когда неэкспортируется ни одно. Набор экспортируемых полей определяет частичный взгляд, инымисловами, открытую проекцию (public projection) на своих клиентов.В клиентских модулях, так же как и в самом модуле, можно определять расширения базовоготипа (например, TextViewers и GraphViewers).
Важен также тот факт, что неэкспортируемыекомпоненты (поля) могут иметь типы, которые не экспортируются. Следовательно, можно весьмаэффективно скрывать определенные типы данных, сохраняя при этом возможность обращения кним со стороны компонентов (скрытых) экспортируемых типов.Включение типовСовременные процессоры отличает поддержка арифметических операций сразу в несколькихчисловых форматах. Желательно, чтобы все эти форматы нашли свое отражение в языке вкачестве атомарных (basic) типов.
Oberon поддерживает 5 из таких форматов:LONGINT, INTEGER, SHORTINT (целочисленные типы)LONGREAL, REAL (вещественные типы)С распространением атомарных типов ослабление между ними правил совместимости становитсяпочти обязательным. (Заметим, что в Modula числовые типы INTEGER, CARDINAL и REAL являютсянесовместимыми.) В связи с этим в язык вводится понятие включения типов (type inclusion). ТипT включает в себя тип T’, если значения типа T’ являются также и значениями типа T.
В Oberonпостулируется следующая иерархия типов:LONGREAL ) REAL ) LONGINT ) INTEGER ) SHORTINTСоответственно ослабляется и правило присваивания. Значение типа T’ может быть присвоенопеременной типа T, если T’ включен в T (или если T’ расширяет тип T), т.е. если T ) T’ или T’ —>T. В этом плане мы возвращаемся (и расширяем) к гибкости языка Algol-60. Пусть, например,даны переменныеi: INTEGER; k: LONGINT; x: REALтогда присваиванияk := i;x := k;x := 1;k := k + 1;x := x * 10 + iМИР ПК – ДИСК.
2003. № 8. СТУДИЯ ПРОГРАММИРОВАНИЯ8/12ИСКУССТВО ПРОГРАММИРОВАНИЯАВГУСТ 2003не противоречат принятым правилам, в то время как операторыi := k; k := xнедопустимы. Присваивание x := k может использовать усечение.Наличие нескольких числовых типов — это очевидная уступка реализациям языка, которые могутвыделять разные объемы памяти для переменных различных типов и которые, тем самым,предоставляют возможность для экономного использования памяти. Этот практический аспект вотношении математической абстракции нельзя игнорировать. Понятие включения типовминимизирует последствия для программиста и требует лишь нескольких неявных инструкцийдля изменения представления данных, такие как знаковые расширения и преобразования целыхв числа с плавающей точкой.Различия между Oberon и пересмотренным Oberon (Revised Oberon)Пересмотр языка Oberon был произведен после большого числа экспериментов в использованиии реализации данного языка.