YLISP 3.7 WIN32
Y L i s p 3.7
Средства объектно-ориентированного программирования
Common LISP Object System (CLOS)
Система YLisp 3.7 включает частичную реализацию CLOS - объектно-ориен-
тированного (ОО) расширения языка Коммон Лисп. Это расширение основано
на концепции класса, множественном наследовании, комбинации методов и
метаобъектах. Классы образуют естественное расширение системы типов
Коммон Лисп. Вместо традиционного механизма передачи сообщений, приня-
того в языке Смолтолк (Smalltalk), используются родовые функции
(generic functions). Методы, ассоциированные с родовыми функциями, вы-
бираются по классам аргументов и осуществляют специфические действия
этих функций.
YLisp полностью реализует механизм наследования, основанный на построе-
нии списка предшествования классов (class precedence list). Последний
является тотальным упорядочением множества, включающего данный класс и
все его надклассы. Список предшествования вычисляется топологической
сортировкой упомянутого множества, исходя из локальных порядков пред-
шествования (local precedence order), заданных в определениях классов.
Иерархия метаобъектов, специфицированная в [CLOS], включает следующие
метаклассы.
Built-in-class Экземпляры данного метакласса представляют основные
встроенные типы YLisp.
Structure-class Экземпляры данного метакласса суть структурные типы,
вводимые с помощью defstruct без указания опции :type.
Standard-class Экземплярами данного метакласса являются классы, опре-
деляемые пользователем с помощью defclass, являющиеся
подклассами класса standart-object.
Безусловно, главный недостаток ОО-системы YLisp - это отсутствие в ней
концепции родовых функций и методов. Он сказывается, в основном, из-за
необходимости мультиметодов. Простейшие же методы, навроде системы пе-
редачи сообщений Смолтолка или функций-членов языка Си++, легко могут
быть смоделированы с помощью разделяемых слотов классов.
Определение класса
~~~~~~~~~~~~~~~~~~
Синтаксис макроса defclass полностью совпадает со стандартным [CLOS].
Однако, имеются следующие семантические отличия и особенности реализа-
ции.
Функции и методы доступа к слотам
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ибо функции доступа, чтения и записи значения слота, специфициро-
ванные посредством опций :accessor, :reader и :writer соответствен-
но, не являются родовыми, нужно избегать употребления одних и тех
же имен для этих функций в определениях различных классов.
В принципе, допустимо использование единой функции доступа к раз-
деляемым (shared) слотам, которые хранятся в различных классах,
но только при условии совпадения имен этих слотов.
Для доступа к локальным (local) слотам экземпляра класса, рекомен-
дуется применять только функции, непосредственно указанные в опре-
делении данного класса. Применение функции доступа к экземпляру
класса C1 для получения значения слота, который описан в надклассе
C2, допустимо, лишь когда C2 наследуется по единственной или самой
правой `генеалогической' ветви, например, так
(defclass C1 (C3 C2) ...)
В этом случае строение экземпляра класса C1 таково, что наследуе-
мые из C2 локальные слоты размещаются раньше (левее), чем слоты,
наследуемые из C3 и его надклассов, которые не являются надкласса-
ми C2. Значит, смещения таких слотов в экземплярах и C1, и C2 сов-
падают.
Задание типа значения слота
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Проверка, соответствует ли значение слота типу, специфицированному
в опции :type, осуществляется при инициализации и реинициализации
зкземпляров посредством initialize-instance и
reinitialize-instance. Когда же слоту присваивается значение путем
(setf (slot-value ...) value)
или
(setf (writer-or-accessor-name ...) value),
соответствие типов не контролируется, а результаты в случае рас-
согласования неопределены, что подтверждается стандартом [CLOS].
Динамическое переопределение
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
YLisp поддерживает динамическое переопределение классов, кото-
рое выражается в следующих моментах.
- Определение класса может быть введено в систему ранее, чем форма
defclass, определяющая один из его надклассов; важно лишь, чтобы
чтобы все надклассы стали определенными, прежде чем будет порож-
ден экземпляр данного класса.
- При переопределении класса, т.е. повторном оценивании соот-
ветствующей формы defclass, происходит переопределение всех его
подклассов.
Однако по двум пунктам данная возможность `не дотягивает' до стан-
дарта [CLOS].
1) Явно переопределенный объект-класс не совпадает по реализации
(по eq) со старым, если число разделяемых слотов в его новом
определении отлично от числа разделяемых слотов в старом. Само
собой, это замечание не относится к автоматически переопределя-
емым подклассам.
2) Экземпляры, уже порожденные к моменту явного или неявного пере-
определения класса, становятся устаревшими и остаются таковыми
до конца своего существования. Новое определение класса будет
влиять лишь на строение его экземпляров, порожденных в будущем.
Модификация устаревших экземпляров в случае неизменности объек-
та-класса (например, посредством initialize-instance) или их
уничтожение (открепление с целью разрешить утилизацию занимае-
мой ими памяти сборщиком мусора) полностью возлагается на
пользователя.
Порождение и инициализация экземпляров
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Создание экземпляра класса осуществляется вызовом функции
make-instance, которая собственно выделяет память (allocate-instance) и
инициализирует (initialize-instance) новый объект. Функция
initialize-instance, наряду с reinitialize-instance, реализована через
shared-initialize.
ОО-система YLisp полностью поддерживает механизм инициализирующих аргу-
ментов (initialization argument list), список которых аналогичен списку
ключевых аргументов и может быть указан при обращении к вышеупомянутым
функциям.
Печатный вид экземпляров
~~~~~~~~~~~~~~~~~~~~~~~~
Печать стандартного объекта, экземпляра standard-object, выполняется
функцией print-object, действие которой можно рассматривать как первич-
ный метод (primary method) для ее `родовой версии'. Она выдает нечто
следующего вида:
#{class-name slot-name1 value1 ... slot-nameN valueN}.
Приведенная форма содержит значения всех связанных слотов экземпляра
класса class-name. Выдача содержимого разделяемых слотов контролируется
значением динамической переменной *print-shared*.
Длина и глубина печатного представления регулируется динамическими пе-
ременными *print-length* и *print-level*, аналогично тому, как это про-
исходит при печати списков и общих векторов.
Поскольку функция print-object не является родовой, постольку не следует
ее переопределять. Настроить программу печати на конкретный класс мож-
но, определив в этом классе разделяемый слот с именем :print-function.
Значением этого слота должен стать функциональный объект с двумя аргу-
ментами:
1) печатаемый экземпляр,
2) выходной поток.
Желательно, чтобы подобные функции учитывали значения переменных
*print-pretty* и *print-escape*. Значение *print-length* может игнори-
роваться, а глубину печати принтер отслеживает сам.
|