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* может игнори- роваться, а глубину печати принтер отслеживает сам. |