28.1 Концепция программного интерфейса

Common Lisp Object System (CLOS [клос]) — это объектно-ориентированное расширение для Common Lisp’а. Объектная система основана на обобщённых функциях, множественном наследовании, декларации сочетания методов и метаобъектном протоколе.

Первые две части данной спецификации описывают программный интерфейс для использования CLOS’а. Первая часть содержит описание концепций CLOS’а, а вторая — описание функций и макросов, используемых в интерфейсе для программиста. Третья часть описывает методы для изменения поведения объектной системы.

Основными объектами CLOS’а являются классы, экземпляры классов, обобщённые функции и методы.

Объект класса отображает структуру и поведение множества других объектов, которые называются экземплярами класса. Каждый объект Common Lisp’а является экземпляром класса. Класс объекта устанавливает ряд операций, которые могут быть выполнены над объектом.

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

Обобщённая функция может использоваться также как и обычная функция. В частности, обобщённая функция может быть аргументом для таких функций, как funcall и apply, и может иметь глобальное имя.

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

Функционал сочетаний методов управляет порядком вызова методов и возвратом значений обобщённой функцией. По-умолчанию CLOS использует стандартный тип сочетания методов, но он также предоставляет возможность определения новых типов сочетаний.

28.1.1 Терминология для ошибок

Ситуация — это выполнение выражения в некотором заданном контексте. Например, ситуацией может быть вызов функции с аргументами, которые не удовлетворяют некоторым заданным ограничениям.

В спецификации CLOS’а описано поведение программ во всех ситуациях. Кроме того указаны некоторые возможности для разработчиков реализаций. Реализация не может расширять синтаксис или семантику объектной системы за исключением явно описанных возможностей. В частности, реализация не может расширять синтаксис объектной системы так, чтобы появлялась двусмысленность между спецификацией и расширением.

Спецификация CLOS может запрещать данные расширения, но разрешая другие.

28.1.2 Классы

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

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

У класса может быть имя. Для получения имени класса может использоваться функция class-name, которая принимает объект класса и возвращает его имя. Имя анонимного класса — nil. Имя класса может задаваться символом. Функция find-class принимает символ и возвращает класс, имя которого заданно символом. Класс имеет собственное имя, если имя является символом и если имя задаёт данный класс. То есть, класс C имеет собственное имя S, если S = (class-name C) и C = (find-class S). Следует отметить, что существует возможность того, что (find-class S1) = (find-class S2) и S1≠S2. Если C = (find-class S), мы говорим, что C является классом с именем S.

Класс C1 является прямым суперклассом класса C2, если C2 в своём определении явно указывает C1 как родительский. В этом случае C2 является прямым подклассом C1. Класс Cn является суперклассом для класса C1 в том случае, если существует ряд классов C2,…,Cn−1 такой, что Ci+1 является прямым суперклассом Ci для 1 ≤i < n. В этом случае C1 является подклассом Cn. Класс никогда не рассматривается как суперкласс или подкласс самого себя. То есть, если C1 является суперклассом C2, тогда C1≠C2. Множество классов, составленное из некоторого класса C и его суперклассов, называется «С и его суперклассы».

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

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

Список приоритетности классов всегда состоит из локальных списков приоритетности классов для каждого класса, перечисленногов первом списка. Классы в каждом локальном списке находятся в том же порядке, что и в общем списке. Если порядок в локальном списке противоречит всем остальным, то список приоритетности не создаётся, и сигнализируется ошибка. Список приоритетности классов и его создание описывается в разделе 28.1.5.

Классы организованы в ориентированный ациклический граф. Существует два наиболее значимых класса: t и standard-object. Класс t не имеет суперклассов (родителей). Он является суперклассом для всех классов, за исключением самого себя. Класс standard-object является экземпляром класса standard-class и является суперклассом для всех классов - экземпляров standard-class, за исключением самого себя.

В языке предусмотрено отображение пространства CLOS классов в пространство типов. Большинство стандартных типов Common Lisp’а имеют соответствующий класс с тем же именем, что и тип. Но некоторые типы соответствия не имеют. Интеграция типов и классов описана в разделе 28.1.4.

Классы представлены объектами, которые в свою очередь являются экземплярами классов. Класс класса объекта называется метаклассом данного объекта. Когда конкретной объект не оговаривается, термин метакласс будет использоваться для обозначения класса, экземпляры которого сами являются классами. Метакласс устанавливает форму наследования, используемую классами, которые являются его экземплярами, и представление экземпляров этих классов. CLOS предоставляет основной метакласс, standard-class, которого вполне достаточно для большинства программ. Метаобъектный протокол предоставляет механизм для определения и использования новых метаклассов.

За исключением явного указания, все классы упомянутые в этой главе являются экземплярами класса standard-class, все обобщённые функции являются экземплярами класса standard-generic-function, и все методы являются экземплярами класса standard-method.

Определение классов

Для определения новых классов используется макрос defclass. Определение класса включает следующие пункты:

Параметры слота и класса формы defclass предоставляют механизм для следующих пунктов:

Создание экземпляров классов

Обобщённая функция make-instance создаёт и возвращает новый экземпляр объекта. Объектная система предоставляет несколько способов для указания того, как будет инициализирован новый экземпляр. Например, можно указать первоначальные значения для слотов с помощью аргументов для make-instance или с помощью указания их в спецификаторе слота.

В методах для обобщённых функций, которые являются частью инициализационного протокола, могут быть выполнены другие действия для инициализации. Весь инициализационный протокол описан в разделе 28.1.9.

Слоты

Объект, метаклассом которого является standard-class, имеет ноль или более слотов. Слоты объекта определяются классом объекта. Каждый слот может содержать одно значение. Имя слота является обычным символом, который может использоваться и для переменной.

Когда слот не имеет значения, слот называется несвязанным. Когда происходит чтение несвязанного слота, вызывается обобщённая функция slot-unbound. По-умолчанию установленный системой главный метод для slot-unbound сигнализирует ошибку.

Значение по-умолчанию для слота устанавливается с помощью параметра слота :initform. Когда для установки значения используется форма :initform, то она вычисляется в том же лексическом окружении, что и defclass. :initform вместе с лексическим окружением, тем же что и для defclass, называется захваченной :initform. Смотрите раздел 28.1.9.

Локальный слот — это слот, который доступен только в рамках одного экземпляра класса. Разделяемый слот — это слот, который одновременно доступен для нескольких экземпляров класса.

Говорится, что класс определяет слот с заданным именем, если форма defclass, которая создаёт класс, содержит спецификатор слота с этим именем. Определение локального слота не создаёт его немедленно. Определение указывает, что слот будет создаваться каждый раз при создании экземпляра класса. Определение разделяемого слота создаёт его немедленно.

Параметр :allocation для defclass указывает тип создаваемого слота. Если значение :allocation равно :instance, тогда слот будет локальным. Если значение :allocation равно :class, тогда слот будет разделяемым.

Слот называется доступным для экземпляра класса, если слот был определён в этом классе, или был унаследован от суперкласса. Заданное имя может указывать на не более чем один слот экземпляра класса. Разделяемый слот доступен для всех экземпляров класса. Подробное описание наследования слотов дано в разделе 28.1.3.

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

К слоту можно получить доступ двумя способами: с помощью использования простой функции slot-value или с помощью обобщённых функций, созданных с помощью формы defclass.

slot-value может быть использована с любым именем слота, которое было определено в форме defclass, для доступа к некоторому слоту, доступному в экземпляре класса.

Макрос defclass предоставляет синтаксис для генерации методов для чтения и записи слотов. Если необходимо чтение, тогда автоматически генерируется метод для чтения значения слота, однако для записи метод не генерируется. Если необходима запись, тогда автоматически генерируется метод для записи значение в слот, однако для чтения метод не генерируется. Если необходим аксессор, тогда автоматически генерируются методы для чтения и для записи. Метода для чтения и записи реализуются с помощью slot-value.

Когда для слота указываются методы чтения и записи, то также указывается имя для обобщённой функции. Если в качестве имени метода указан символ name, тогда именем обобщённой функции для записи слота является символ name, и обобщённая функция принимает два аргумента: новое значение и экземпляр класса, соответственно. Если имя указанное для аксессора является символом name, имя обобщённой функции для чтения слота является символом name, и имя обобщённой функции для записи слота является символом списком (setf name).

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

Следует отметить, что slot-value может использоваться для чтения или записи значение в слот вне зависимости от того, существуют ли метода для чтения или записи слота. Когда используется slot-value, методы чтения или записи не вызываются.

Макрос with-slots может быть использован для установки лексического окружения, в котором указанные слоты доступны лексически как если бы они были просто переменными. Макрос with-slots вызывает функцию slot-value для доступа к указанным слотам.

Макрос with-accessors может быть использован для установки лексического окружения, в котором указанные слоты доступны лексически с помощью аксессоров как если бы они были просто переменными. Макрос with-accessors вызывает соответствующие аксессоры для доступа к указанным слотам. Любой аксессор, указанный в with-accessors должен быть уже доступным перед использованием.

28.1.3 Наследование

Класс может наследовать методы, слоты и другие параметры формы defclass от своих суперклассов (родительских классов). Следующие разделы описывают наследование методов, слотов и их параметров, и параметров класса.

Наследование методов

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

Наследование методов работает одинаково вне зависимости от того, был ли метод создан с помощью формы определения метода или с помощью параметра формы defclass, который указал на автоматическую генерацию метода.

Подробное описание наследования методов дано в разделе 28.1.7.

Наследование слотов и их параметров

Множество имен всех слотов доступных в экземпляре класса C является объединением множеств слотов определяемых классом С и его суперклассами. Структура экземпляра — это множество имён локальных слотов в данном экземпляре.

В простейшем случае, среди класса C и его суперклассов только один из них определяет слот с заданным именем. Если слот определён в суперклассе C, слот называется унаследованным. Характеристики слота установлены с помощью спецификатора слота в определяющем его классе. Рассмотрим класс определяющий слот S. Если значение параметра слота :allocation равно :instance, тогда S является локальным слотом и каждый экземпляр класса C имеет свой собственный слот с именем S. Если значение параметра слота :allocation равно :class, тогда S является разделяемым слотом, класс, который определял S хранит значение слота, и все экземпляры C могут получить доступ к этому слоту. Если параметр слота :allocation не указан, по-умолчанию используется :instance.

В общем случае, среди классов C и его суперклассов определять слот с заданным именем могут несколько классов. В таких случаях в экземпляре класса C будет доступен только один слот, и свойства этого слота будут комбинацией нескольких спецификаторов слотов в соответствие с пунктами:

Следствие правила назначения типа слота (разделяемого, локального) заключается в том, что разделяемый слот может быть затенён. Например, если класс C1 определяется слот с именем S, значение параметра :allocation которого равно :class, то этот слот будет доступен для всех экземпляров C1 и его подклассов. Однако, если C2 является подклассом C1 и также определяет слот с именем S, слот из C1 не будет разделяемым между экземплярами C2 и его подклассами. Если класс C1 определяет разделяемый слот, тогда любой подкласс C2 для C1 будет иметь этот разделяемый слот, пока форма defclass для C2 не определит слот с тем же именем, или C2 не получит суперкласс приоритетнее суперкласса C1, который определяет слот с таким же именем.

Следствие из правила указания типа содержимого слота заключается в том, что значение слота удовлетворяет ограничениям типа каждого спецификатора слота в данном классе и всех его суперклассов. Так как результат попытки сохранения значения, которое не удовлетворяет типу, не определён, то слот может случайно содержать это значение.

Параметры слота :reader, :writer и :accessor создают методы, а не определяют какие-либо свойства слота. Наследование методов чтения и записи описано в разделе 28.1.3.

Методы, которые выполняют доступ к слотам, используют только имя слота и тип значения слота. Предположим, что суперкласс предоставляет метод, который обеспечивает доступ к разделяемому слота с некоторым именем, и подкласс определяет локальный слот с этим же именем. Если метод, представленный от суперкласса, используется для экземпляра подкласса, метод будет оперировать с локальным слотом.

Наследование параметров класса

Параметр класса :default-initargs наследуется. Множество всех первоначальных аргументов класса является объединением множеств этих аргументов для данного класса и всех его суперклассов. В случае коллизий выбирается значение наиболее близкого класса.

Если некоторый параметр класса :default-initargs определяет аргумент с одним и тем же именем более одного раза, сигнализируется ошибка.

Примеры

(defclass C1 ()
  ((S1 :initform 5.4 :type number)
   (S2 :allocation :class)))

(defclass C2 (C1)
  ((S1 :initform 5 :type integer)
   (S2 :allocation :instance)
   (S3 :accessor C2-S3)))

Экземпляры класса C1 содержат локальный слот с именем S1, значение по-умолчанию которого 5.4 и это значение всегда должно быть числовым. Класс C1 также содержит разделяемый слот с именем S2.

В экземплярах класса C2 будут присутствовать слоты с именем S1. Первоначальное значение по-умолчанию для слота S1 равно 5. Значение слота S1 принадлежит типу (and integer number). В экземплярах C2 также будут присутствовать слоты с именами S2 и S3. Класс C2 также имеет метод C2-S3 для чтения значения слота S3. Данный метод также используется для записи значения в слот S3.

28.1.4 Интеграция типов и классов

CLOS отображает пространство классов в пространство типов Common Lisp’а. Каждый класс, который имеет имя собственное, имеет соответствующий одноимённый тип.

Собственное имя любого класса является корректным спецификатором типа. Кроме того, каждый объект класса (не экземпляр) является корректным спецификатором типа. Таким образом выражение (typep object class) вычисляется в истину, если класс object является собственно классом class или подклассом для class. Вычисление выражения (subtypep class1 class2) возвращает значения t и t, если class1 является подклассом class2 или если они являются одним и тем же классом, иначе выражение возвращает значения nil и t. Если I является экземпляром некоторого класса C с именем S и C является экземпляром метакласса standard-class, тогда вычисление выражения (type-of I будет возвращать S, если S является собственным именем C. Если S не является именем собственным C, выражение (type-of I) будет возвращать C.

Так как имена классов и объекты (не экземпляры) классов является спецификаторами типов, они могут быть использованы в специальном операторе the и в декларациях типов.

Многие, но не все, определённые стандартом спецификаторы типов имеют соответствующие классы с тем же именем собственным. Эти типы перечислены в таблице 28.1. Например, тип array имеет соответствующий одноимённый класс. Спецификаторы типов, представленные списками, не имеют соответствующих классов. Форма deftype не создаёт никаких классов.

Любой класс, который соответствует определённому стандартом спецификатору типа, может быть реализован тремя способами на усмотрение реализации. Он может быть стандартным классом (типов класса, определяемым с помощью defclass), классом структуры (определяемым с помощью defstruct), или встроенным классом (реализованным специальным нерасширяемым способом).

Встроенный класс тот, экземпляры которого имеют ограниченные возможности или специальные представления. При попытке использовать defclass для определения подкласса для встроенного класса будет сигнализирована ошибка. При вызове make-instance для создания экземпляра встроенного класса будет сигнализирована ошибка. При вызове slot-value для создания экземпляра встроенного класса будет сигнализирована ошибка. При переопределении встроенного класса или использовании change-class для изменения класса экземпляра в или из встроенного класса будет сигнализирована ошибка. Однако встроенные классы могут быть использованы в качестве специализатора параметра в методах.

Существует возможность определения является ли класс встроенным с помощью проверки метакласса. Стандартный класс является экземпляром метакласса standard-class, встроенный класс является экземпляром метакласса built-in-class, и класс структуры является экземпляром метакласса structure-class.

Любой тип структуры, созданный с помощью defstruct без использования параметра :type, имеет соответствующий класс. Этот класс является экземпляром метакласса structure-class.

Параметр :include defstruct создаёт прямой подкласс класса, соответствующего наследуемой структуре.

Много стандартных Common Lisp’овых спецификаторов типов имеют соответствующие классы для того, чтобы пользователи могли писать методы, которые различаются на основе этих типов.

Иерархические отношения между спецификаторами типов зеркально отражены на отношения между классами, соответствующим данным типам. Существующая иерархия типов используется для установки списков приоритетности классов для каждого класса, соответствующего Common Lisp’овому типу.

В таблице 28.1 перечислены все классы, которые имеют соответствующие спецификаторы типов. Суперклассы для каждого такого класса представлены в порядке от наиболее близкого к наиболее дальнему, или просто в порядке приоритетности. По этой таблице может быть составлен локальный список приоритетности классов для каждого такого класса, который соответствует типу.

Отдельные реализации могут расширять этот список добавляя другие спецификаторы типов и соответствующие классы. Отдельные реализации могут расширять список приоритетности классов при условии, что не нарушится иерархия отношений типов и дизъюнкция, описанная в разделе 2.15. Стандартный класс, определённый без прямых суперклассов, гарантированно не пересекается с другими классами в таблице, за исключением класса с именем t. FIXME

[At this point the original CLOS report specified that certain Common Lisp types were to appear in table 28.1 if and only if X3J13 voted to make them disjoint from cons, symbol, array, number, and character. X3J13 voted to do so in June 1988 . I have added these types and their class precedence lists to the table; the new types are indicated by asterisks.—GLS]


Таблица 28.1: Родительские классы для отображения Common Lisp’овых типов

Common Lisp’овый типСписок приоритетности классов для соответствующего типа/класса
array (array t)
bit-vector (bit-vector vector array sequence t)
character (character t)
complex (complex number t)
cons (cons list sequence t)
float (float number t)
function * (function t)
hash-table * (hash-table t)
integer (integer rational number t)
list (list sequence t)
null (null symbol list sequence t)
number (number t)
package * (package t)
pathname * (pathname t)
random-state * (random-state t)
ratio (ratio rational number t)
rational (rational number t)
readtable * (readtable t)
sequence (sequence t)
stream * (stream t)
string (string vector array sequence t)
symbol (symbol t)
t (t)
vector (vector array sequence t)

[An asterisk indicates a type added to this table as a consequence of a portion of the CLOS specification that was conditional on X3J13 voting to make that type disjoint from certain other built-in types .—GLS]


28.1.5 Определение списка приоритетности классов

Форма defclass содержит список упорядоченных прямых суперклассов для задаваемого класса. Этот список называется локальным списком приоритетности классов. Это отсортированный список из класса и его прямых суперклассов. Список приоритетности классов для класса C является упорядоченным списком из класса C и всех его суперклассов, который согласуется с локальными списками для C и его суперклассов.

Класс имеет приоритет перед его прямыми суперклассами, и прямой суперкласс стоит перед всеми другими прямыми суперклассами указанными правее в форме defclass. Для любого класса C, истинно определение RC = {(C,C1), (C1,C2),…, (Cn−1,Cn)} , где C1,…,Cn — прямые суперклассы для C в порядке, в котором они перечислены в форме defclass. Данные упорядоченные пары образуют упорядоченный список из класса C и его прямых суперклассов.

Пусть SC является множеством из C и его суперклассов. Пусть R является R = ⋃ c ∈ S CRc

Множество R может образовывать или не образовывать частичное упорядочение, в зависимости от того, согласованы ли Rc, c ∈ SC. Предполагается, что они согласованы, и что R образовывает частичное упорядочение. Когда Rc не согласовано, говориться, что R несогласовано. FIXME

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

Топологическая сортировка

Топологическая сортировка

Топологическая сортировка осуществляет поиск такого класса C в Sc, если в соответствии с элементами R никакой другой класс не предшествует данному. Класс C размещается в самом начале результирующего списка. Удаляется C из Cc и удаляются все пары вида (C,D), D ∈ SC из R. Процесс повторяется, в конец результирующего списка добавляются классы без предшествующих классов. Остановка происходит, когда элементов, у которых нет предшествующих элементов, больше не осталось.

Если множество SC не пустое и процесс сортировка завершился, множество R является несолгасованным. Если каждый класс в конечном множестве классов имеет предшествующие классы, тогда R содержит циклическую структуру. Это значит, что существует такая цепочка классов C1,…,Cn, что Ci имеет предшествующий Ci+1, 1 ≤ i < n, и Cn предшествует C1.

Иногда случается так, что в SC есть несколько классов без предшественников. В этом случае выбирается один, который имеет прямой подкласс, стоящий наиболее правее в вычисленном списке предшествующих классов. Если такого класса нет, R не образовывает частичное упорядочение — Rc, c ∈ SC не согласованы.

Более подробно, пусть {N1,…,Nm}, m ≥ 2 будут классами из SC без предшественников. Пусть (C1…Cn), n ≥ 1, будут вычисленным списком приоритетности классов. C1 — наиболее специфичный класс, и Cn — наименее специфичный. Пусть 1 ≤ j ≤ n будет наибольшим числом таким, что существует i, где 1 ≤ i ≤ m и Ni является прямым суперклассом Cj, Ni находится рядом.

Результат этого правила выбора из множества класса без предшественников заключается в том, что классы в простой цепочке суперклассов являются смежными в списке приоритетности классов, и эти классы в каждом относительно раздельном подграфе являются смежными в списке приоритетности классов. Например, пусть T1 и T2 будут подграфами, у которого общим элементом является только J. Предположим, что для J не ни одного суперкласса ни в T1, ни в T2. Пусть C1 будет нижней частью T1 и C2 — нижней частью T2. Предположим C является классом, у которого прямые суперклассы C1 и C2 вот в этом порядке. Тогда список приоритетности класса для C будет начинаться с C и продолжаться всеми классами из T1 за исключением J. Следующими будут классы из T2. Класс J и его суперклассы будут последними.

Примеры

Данный пример устанавливает список родительских классов для класса pie. Классы определены следующим образом:

(defclass pie (apple cinnamon) ())
(defclass apple (fruit) ())
(defclass cinnamon (spice) ())
(defclass fruit (food) ())
(defclass spice (food) ())
(defclass food () ())

Множество S={pie, apple, cinnamon, fruit, spice, food, standard-object, t}. Множество R={(pie, apple), (apple, cinnamon), (cinnamon, standard-object), (apple, fruit), (fruit, standard-object), (cinnamon, spice), (spice, standard-object), (fruit, food), (food, standard-object), (spice, food), (standard-object, t)}.

Класс pie не имеет предшественников, поэтому он идёт первым. Результат пока что просто (pie). Происходит удаление pie из S и пар, содержащих pie, из R. Получается: S={apple, cinnamon, fruit, spice, food, standard-object, t} and R={ (apple, cinnamon), (cinnamon, standard-object), (apple, fruit), (fruit, standard-object), (cinnamon, spice), (spice, standard-object), (fruit, food), (food, standard-object), (spice, food), (standard-object, t)}.

Класс apple не имеет предшественников, поэтому он следующий. Результат теперь (pie apple). Происходит удаление apple из S и пар, содержащих pie, из R. Получается: S={cinnamon, fruit, spice, food, standard-object, t} and R={(cinnamon, standard-object), (fruit, standard-object), (cinnamon, spice), (spice, standard-object), (fruit, food), (food, standard-object), (spice, food), (standard-object, t)}.

Классы cinnamon и fruit не имеют предшественников, поэтому следующим будет тот, у которого прямые подклассы в вычисленном списке приоритетности классов стоят правее. Класс apple прямой подкласс fruit, и класс pie прямой подкласс cinnamon. Так как apple стоит правее чем pie в списке приоритетности, следующим становиться fruit и результат выглядит (pie apple fruit). S={cinnamon, spice, food, standard-object, t}; R={(cinnamon, standard-object), (cinnamon, spice), (spice, standard-object), (food, standard-object), (spice, food), (standard-object, t)}.

Класс cinnamon следующий, и результат получается (pie apple fruit cinnamon). В данный момент: S={spice, food, standard-object, t}; R={(spice, standard-object), (food, standard-object), (spice, food), (standard-object, t)}.

Затем добавляются классы spice, food, standard-object и t в таком же порядке. Результат списка приоритетности классов для pie является

(pie apple fruit cinnamon spice food standard-object t)

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

(defclass new-class (fruit apple) ())
(defclass apple (fruit) ())

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

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

(defclass pie (apple cinnamon) ())
(defclass pastry (cinnamon apple) ())
(defclass apple () ())
(defclass cinnamon () ())

Список приоритетности классов для pie

(pie apple cinnamon standard-object t)

Список приоритетности классов для pastry

(pastry cinnamon apple standard-object t)

В упорядочении суперклассов для pie apple может быть приоритетнее cinnamon, но в упорядочении pastry это невозможно. Так или иначе, создать новый класс, у которого два суперкласса pie и pastry, невозможно.

28.1.6 Обобщённые функции и методы

Обобщённая функция — это функция, поведение которой зависит от передаваемых аргументов. Методы определяют поведение и операции этой функции для конкретных классов аргументов. Следующий раздел описывает обобщённые функции и методы.

Введение в обобщённые функции

Объект обобщённой функции содержит множество методов, лямбда-список, тип сочетания методов и другую информацию.

Как и обычная Lisp’овая функция, обобщённая функция принимает аргументы, выполняет последовательность операций, и, возможно, возвращает полезные значения. Обычная функция имеет одно тело с кодом, которое выполняется во всех случаях вызова функции. Обобщённая функция имеет множество тел с кодом, и при вызове для исполнения из данного множества выделяется подмножество. Выбранное подмножество и порядок этих тел устанавливается с помощью класса(ов) одного или нескольких переданных аргументов и типа сочетания методов.

Синтаксис вызова обычной и обобщённой функции одинаков.

Обобщённые функции являются обычными функциями. Их можно передать в качестве аргументов, вернуть в качестве результата, и использовать первым аргументом в funcall и apply, и вообще везде, где можно использовать обычные функции.

Макрос generic-function создаёт анонимную обобщённую функцию со множеством методов, указанных в форме generic-function.

При вычислении формы defgeneric происходит одно из трёх действий:

Некоторые формы содержат параметры обобщённой функции, такие как тип сочетания методов или порядок приоритета аргументов. Они будут называться, как «формы, которые указывают параметры обобщённой функции». Это формы: defgeneric, generic-function.

Некоторые формы определяют методы для обобщённой функции. Они называются «формы определяющие методы». Это формы: defgeneric, defmethod, generic-function и defclass. Следует отметить, что все эти формы за исключением defclass и defmethod также определяют параметры обобщённой функции.

Введение в методы

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

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

Форма, определяющая метод, содержит код, который будет выполнен, когда аргументы обобщённой функции такие, что должен быть вызван данный метод. Когда такая форма вычисляется, создаётся объект метода и происходит одно из четырёх действий:

Если лямбда-список нового метода не соответствует лямбда-списку обобщённой функции, сигнализируется ошибка. Если форма, определяющая метод и которая не может задавать параметры обобщённой функции, создаёт новую обобщённую функцию, лямбда-список для этой обобщённой функции будет производным от и согласованным с лямбда-списком формы, которая определила метод. Для детального описания согласованности, смотрите раздел 28.1.6.

Каждый метод имеет специализированный лямбда-список, который устанавливает, в каких случаях данный метод может быть применён. Специализированный лямбда-список похож на обычный, однако содержит специализированные параметры, которые могут быть использованы на месте обычных обязательных параметров. Специализированный параметр является списком (variable-name parameter-specializer-name), где parameter-specializer-name является или именем класса, или списком (eql form). Имя специализатора параметра означает следующее:

Имена специализаторов параметров используются в макросах, предназначенных для пользовательского интерфейса (defmethod), тогда как специализаторы параметров используются в функциональном интерфейсе.

[It is very important to understand clearly the distinction made in the preceding paragraph. A parameter specializer name has the form of a type specifier but is semantically quite different from a type specifier: a parameter specializer name of the form (eql form) is not a type specifier, for it contains a form to be evaluated. Type specifiers never contain forms to be evaluated. All parameter specializers (as opposed to parameter specializer names) are valid type specifiers, but not all type specifiers are valid parameter specializers. Macros such as defmethod take parameter specializer names and treat them as specifications for constructing certain type specifiers (parameter specializers) that may then be used with such functions as find-method.—GLS]

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

В рамках заданной обобщённой функции и списка аргументов, применимый метод — это метод для данной функции, у которого специализаторы параметров удовлетворяют соответствующим аргументам. Следующее определение разъясняет, что имеется ввиду под применимым методом и под аргументом, удовлетворяющим специализатору параметра.

Пусть ⟨A1,…,An будут обязательными аргументами обобщённой функции. Пусть ⟨P1,…,Pn будут специализаторами параметров, соответствующие обязательным параметрам метода M. Метод M является применимым, когда Ai удовлетворяет Pi. Если Pi является классом, и если Ai является экземпляром класса C, тогда говорится, что Ai удовлетворяет Pi, когда C = Pi или когда C является подклассом Pi. Если Pi является формой (eql object), тогда говорится, что Ai удовлетворяет Pi, когда функция eql применённая к Ai и object истинна.

Так как специализатор параметра является спецификатором типа, во время выбора метода для определения того, удовлетворяют ли аргумента спецификатору параметра может использоваться функция typep. Специализатор параметра не может быть списком спецификатора типа, таким как (vector single-float). Специализатор параметра может быть только таким списком как (eql object). Common Lisp определяет спецификатор типа eql, как если бы было выполнено следующее выражение:

(deftype eql (object) ‘(member ,object))

[Смотрите раздел  4.3.—GLS]

Метод, у которого все специализаторы параметров являются классами t, называется метод по-умолчанию. Такое метод применим всегда, но может быть скрыт с помощью более специализированного метода.

Методы могут иметь квалификаторы, которые указывают процедуре сочетания методов способ вызова методов. Метод, который имеет один или более квалификатор, называется квалифицированным методом. Метод без квалификаторов называется неквалифицированным методом. Квалификатором является любой объект, кроме списка, т.е. любой не-nil атом. Квалификаторы, определённые с помощью стандартного сочетания методов и системного типа сочетания методов, являются символами.

В данной спецификации, термины главный метод и вспомогательный метод используются для разделения методов по типу сочетания. В стандартном типе сочетания методов, главные методы это неквалифицированные методы, и вспомогательные методы это методы с одним из квалификаторов :around, :before или :after. Когда определяется тип сочетания методов с помощью краткой формы define-method-combination, главные методы это те, которые квалифицированы с помощью имени сочетания методов, и вспомогательные методы имеют квалификатор :around. Таким образом определение терминов главный метод и вспомогательный метод имеет значение относительно текущего типу сочетания методов.

Согласованность в специализаторах параметров и квалификаторах

Два метода согласованы друг с другом в специализаторах параметров и квалификаторах, если выполняются следующие условия:

Гармония лямбда-списков для всех методов обобщённой функции

Следующие правила определяют гармонию множества лямбда-списков, включая лямбда-списки каждого метода некоторой обобщённой функции и лямбда-список определённый для самой этой функции, если он, конечно, был указан.

Если форма, которая определяет метод и не может указывать параметры обобщённой функции, создаёт обобщённую функцию, и если лямбда-список для метода содержит именованные параметры, тогда лямбда-список обобщённой функции будет содержать символ &key (но сами параметры содержать не будет).

Именованные параметры в обобщённых функциях и методах

Когда обобщённая функция или любой из её методов содержат в своих лямбда-списках &key, определённый набор именованный параметров, принятых обобщённой функцией, меняется в зависимости от применяемых методов. Набор именованных параметров, принимаемых обобщённой функцией для отдельного вызова, является объединением всех именованных параметров, принимаемых всеми применимыми методами, и именованных параметров, указанных после &key в определении обобщённой функции. Метод, который содержит &rest, но не содержит &key, не попадает в список применимых методов. Если лямбда-список любого применимого метода или обобщённой функции содержит &allow-other-keys, обобщённая функция принимает все именованные параметры.

Правила согласования лямбда-списков требуют, чтобы каждый метод принимал все именованные параметры, указанные после &key в определении обобщённой функции, причём делал это явно указанием &allow-other-keys или указанием &rest, но не &key. Каждый метод может принимать свои дополнительные именованные параметры в дополнение к тем, которые были в определении обобщённой функции.

Если в обобщённую функцию передан именованный параметр такой, что применимого метода найдено не было, генерируется ошибка.

Например, определим два метода для width.

(defmethod width ((c character-class) &key font) ...)

(defmethod width ((p picture-class) &key pixel-size) ...)

Предположим, что больше методов нет, и определения обобщённой функции для width тоже нет. Вычисление следующей формы сигнализирует ошибку, потому что именованный параметр :pixel-size не может быть передан в применяемый метод.

(width (make-instance ’character-class :char #\Q)
       :font ’baskerville :pixel-size 10)

Вычисление следующей формы сигнализирует ошибку.

(width (make-instance ’picture-class :glyph (glyph #\Q))
       :font ’baskerville :pixel-size 10)

Вычисление следующей формы не сигнализирует ошибку, если класс character-picture-class является подклассом и picture-class и character-class.

(width (make-instance ’character-picture-class :char #\Q)
       :font ’baskerville :pixel-size 10)

28.1.7 Выбор и сочетание методов

Когда общая функция вызывается с конкретными аргументами, она должна определить какой код выполнять. Этот код называется действующий (рабочий) метод для этих аргументов. Действующий метод является сочетанием применимых методов в обобщённой функции. Сочетание методов — это Lisp’овое выражение, которые содержит вызовы к некоторым или всем методам. Если обобщённая функция вызвана и при этом методов для применения найдено не было, вызывается обобщённая функция no-applicable-method.

Когда установлен действующий метод, он вызывает с теми же аргументами, что были переданы в обобщённую функцию. Все значения возвращённые этим методом, возвращаются из обобщённой функции.

Определение действующего метода

Действующий метод для набора аргументов устанавливается следующими тремя шагами:

  1. Выбор применимых методов.
  2. Сортировка применимых методов в порядке приоритетности, размещение первым наиболее специфичного метода.
  3. Применение процедуры сочетания методов к сортированному списку применимых методов, создание действующего метода.

Выбор применимых методов Этот шаг описан в разделе 28.1.6.

Сортировка применимых методов в порядке приоритетности. Для сравнения того, кто из двух методов приоритетнее, по порядку исследуются их специализаторы параметров. По-умолчанию порядок исследования слева направо, но в параметре :argument-precedence-order в функции defgeneric или в любой другой форме, задающей параметры обобщённой функции, можно изменить этот порядок.

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

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

Если один из специализаторов выглядит (eql object), то его содержащий метод будет приоритетнее другого метода. Если оба специализатора являются формами eql, то они должны быть одинаковыми (иначе два метода не могли бы быть применимыми к этому аргументу).

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

Применение процедуры сочетания методов к отсортированному списке применимых методов.

В простейшем случае — если используется стандартное сочетание методов и все применимые методы являются главными — рабочим методом является наиболее специфичный метод. Этот метод, используя функцию call-next-method, может вызвать следующий наиболее специфичный метод. Метод, который будет вызван функцией call-next-method, называется следующим методом. Предикат next-method-p проверяет существование следующего метода. Если вызывается call-next-method, а следующего метода нет, тогда вызывается обобщённая функция no-next-method.

В общем случае, рабочий метод — это некоторое сочетание применимых методов. Она определяется Lisp’овой формой, которая содержит вызовы к некоторым или всем применимым методам, и возвращает значение(я), которые будут возвращены из обобщённой функции, и опционально делает доступными через call-next-method некоторые методы. Эта Lisp’овая форма является телом рабочего метода. Она расширяется соответствующим лямбда-списком для создания из него функции.

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

Когда используется стандартный тип сочетания методов и используются квалифицированные методы, рабочий метод составляется так, как описано в разделе 28.1.7.

Другой тип сочетания методов может быть указан с помощью параметра :method-combination функции defgeneric или любой другой формы, определяющей параметры обобщённой функции. В таком случае данный шаг процедуры может быть изменён.

Новые типы сочетания методов могут быть определены с помощью макроса define-method-combination.

Уровень метаобъектов также содержит механизм для определения новых типов сочетаний методов. Обобщённая функция compute-effective-method принимает в качестве аргументов: обобщённую функцию, объект сочетания методов, и отсортированный список применимых методов. Она возвращает Lisp’овую форму, которая определяет рабочий метод. Метод для compute-effective-method может быть определён напрямую с использованием defmethod или не напрямую с использованием define-method-combination. Объект сочетания методов — это объект, который инкапсулирует тип сочетания методов и параметры указанные в параметре :method-combination в формах, которые указывают параметры для обобщённых функций. _______________________________________________

Заметка для реализации:

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

__________________________________________________________________________

Стандартное сочетание методов

Стандартное сочетание методов поддерживается классом standard-generic-function. Оно используется, если не указаны другие типы сочетаний методов или если указан встроенный standard тип сочетания методов.

Главные методы определяют главное действие рабочего метода, тогда как вспомогательные методы изменяют это действие одним из трёх способов. Главные методы не имеют квалификаторов.

Вспомогательный метод — это метод, у которого квалификатор является :before, :after или :around. Стандартное сочетание методов позволяет использовать максимум один квалификатор для метода, в случае использования более одного квалификатора, сигнализируется ошибка.

Семантика стандартного сочетания методов:



Изображение 28.1: Common Lisp Standard Method Invocation by Rainer Joswig

PIC


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

:before методы запускаются в порядке от наиболее к наименее специфичному и :after методы запускаются в порядке от наименее к наиболее специфичному. Обоснование данного механизма может быть показан в следующем примере. Предположим класс C1 изменяет поведение своего суперкласса C2 добавлением :before и :after методов. Определено ли поведение класса C2 напрямую методами для C2 или унаследовано от его суперклассов не влияет на относительные вызовы методов для экземпляра класса C1. :before метод класса C1 вызывается перед всем методами класса C2. :after метод класса C1 вызывается после всех методов класса C2.

Методы :around вызываются перед тем, как будет вызван любой другой метод. Таким образом наименее специфичный :around метод вызывается перед наиболее специфичным главным методом.

Если используются только главные методы, и не используется call-next-method, вызываться будет только самый специфичный метод. То есть наиболее специфичный метод скрывает более общий.

Декларация сочетания методов

Макрос define-method-combination определяет новые формы сочетаний методов. Он предоставляет механизм для изменения создания рабочего метода. Процедура по-умолчанию создания рабочего метода описана в разделе 28.1.7 Существует две формы define-method-combination. Краткая форма содержит простую функциональность. Длинная форма более мощна и подробна. Длинная форма похожа на defmacro в том, что тело является выражением. которое вычисляет Lisp’овую форму. Макрос предоставляет механизм для реализации произвольного функционала в сочетании методов и для произвольной обработки квалификаторов методов. Синтаксис и использование обеих форм макроса define-method-combination описаны в разделе 28.2.

Системные типы сочетания методов

CLOS предоставляет множество системных типов сочетания методов. Для того, чтобы указать, что обобщённая функция использует один из этих типов, необходимо в параметр :method-combination для функции defgeneric или любой другой, задающей параметры для обобщённой функции, указать имя типа сочетания методов.

Имена системных типов сочетания методов: +, and, append, list, max, min, nconc, or, progn и standard.

Семантика системного standard типа сочетания методов было описано в разделе 28.1.7. Другие системные типы сочетания методов называются простыми системными типами сочетания методов.

Простые системные типы сочетания методов работают так, как если бы были определены в краткой форме define-method-combination. Они предоставляют для методов две роли:

Семантика простых системных типов сочетания методов:

Простые системный типы сочетания методов требуют только один квалификатор для метода. В случаях, если существуют применимые методы без квалификаторов или с неподдерживающимися квалификаторами, сигнализируется ошибка. Если существуют применимые :around методы, и не существует главных, также сигнализируется ошибка.

28.1.8 Метаобъекты

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

Метаклассы

Метакласс объекта — это класс класса данного объекта. Метакласс устанавливает представление экземпляров от экземпляров этого метакласса и формы наследования, используемые этими экземплярами для описаний слотов и наследования методов. Механизм метаклассов может использоваться для оптимизации или модификации объектной системы. Протокол для определения метаклассов в третьей части CLOS спецификации: Метаобъектный протокол CLOS 30.

Стандартные метаклассы

CLOS содержит ряд уже определённых метаклассов. В них входят классы standard-class, built-in-class и structure-class:

Стандартные метаобъекты

Объектная система содержит стандартный набор метаобъектов, называемых стандартные метаобъекты. Они включают в себя класс standard-object и экземпляры классов standard-method, standard-generic-function и method-combination.

28.1.9 Создание и инициализация объекта

Обобщённая функция make-instance создаёт и возвращает новый экземпляр класса. Первый аргумент является классом или его именем, и остальные аргументы формируют список инициализационных аргументов.

Инициализация нового экземпляра состоит из нескольких раздельных шагов: объединение явно указанных инициализационных аргументов со значениями по-умолчанию для неуказанных аргументов, проверка корректности инициализационных аргументов, выделение место для хранения экземпляра, заполнение слотов значениями и выполнение пользовательских методов, позволяющих выполнить дополнительную инициализацию. Каждый шаг make-instance реализуется с помощью обобщённой функции, обеспечивая механизм для настройки этого шага. Кроме того, make-instance сама по себе является обобщённой функцией и, следовательно, также может быть настроена.

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

Инициализационные аргументы

Инициализационный аргумент управляет созданием и инициализацией объекта. Обычно для его имени удобно использовать ключевой символ, но может использоваться любой символ, включая nil. Инициализационный аргумент может использоваться двумя методами: для заполнения слота значением, или для предоставления аргумента в инициализационном методе. Для обоих методов может использовать один инициализационный аргумент.

Список инициализационных аргументов — это список чередующихся имён инициализационных аргументов и их значений. Его структура идентична списку свойств и также части списка аргументов для &key параметров. Как и в тех списках, если инициализационный аргумент в списке встречается более чем один раз, для значения используется наиболее левое вхождение, остальные игнорируются. Аргументы для make-instance (после первого аргумента) формируют список инициализационных аргументов. Проверка имён инициализационных аргумент отключена, если в списке встречает пара :allow-other-keys и не-nil.

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

Инициализационный аргумент может быть связан с методом. Когда создаётся объект и указан такой инициализационный аргумент, то обобщённые функции initialize-instance, shared-initialize и allocate-instance вызываются с этим аргументом в виде пары: ключевой параметр и его значение. Если значение для инициализационного аргумента на было указано в списке, лямбда-список метода используется значение по-умолчанию.

Инициализационные аргументы используются в четырёх случаях: при создании экземпляра класса, при переинициализации экземпляра, при обновлении экземпляра в соответствие с переопределённым классом, и при обновлении экземпляра в соотвествие с определением другого класса.

Поскольку инициализационные аргументы используются для управления созданием и инициализацией экземпляра того или иного класса, мы говорим, что инициализационный аргумент — это «инициализационный аргумент для» этого класса.

Декларация корректных инициализационных аргументов FIXME

Инициализационные аргументы проверяются на корректность в каждой из четырёх ситуаций их использования. Инициализационный аргумент может быть корректен в одном случае и некорректен в другом. Например, системный главный метод для make-instance, определённый для standard-class, проверяет корректность инициализационных аргументов и сигнализирует ошибку, если инициализационный аргумент указан и является некорректным в данной ситуации.

Существует два случая декларации корректного инициализационного аргумента.

Корректные инициализационные аргументы это те, которые устанавливают значения в слоты или те, что передают значения в методы вместе с предопределённым аргументом :allow-other-keys. Значение по-умолчанию для :allow-other-keys равно nil. Значение :allow-other-keys здесь такое же, как и в случае использования в обычной функции.

Значения по-умолчанию для инициализационных аргументов

Форма значения по-умолчанию для инициализационного аргумента может быть указана с помощью параметра класса :default-initargs. Если инициализационный аргумент задекларирован в некотором классе, его значение по-умолчанию может быть обозначено в другом классе. В этом случае :default-initargs используется для установки значения по-умолчанию для унаследованного инициализационного аргумента.

Параметр :default-initargs используется только для указания значений по-умолчанию. Он не декларирует символ в качестве корректного имени инициализационного аргумента. Кроме того, параметр :default-initargs используется только для указания значений по-умолчанию при создании экземпляра объекта.

Аргумент к параметру класса :default-initargs является списком чередующихся имён инициализационных аргументов и форм. Каждая форма является значением по-умолчанию для соответствующего инициализационного аргумента. Форма значения по-умолчанию инициализационного аргумента используется и вычисляется только, если этот аргумент не встречается в make-instance и значение по-умолчанию не переопределено в более специфичном классе. Форма значения по-умолчанию вычисляется в лексическом окружении defclass, в котором она была указана. Результат вычисления используется для установки в инициализационный аргумент.

Инициализационные аргументы, указанные в make-instance, объединяются с аргументами, которые имеют значения по-умолчанию, и получается список инициализационных аргументов со значениями по-умолчанию. Этот список является списком чередующихся имён инициализационных аргументов и значений. Вначале идут пары со значениями переданными в функцию, затем — со значениями по-умолчанию. Значения по-умолчанию упорядочены в соотвествие со списком приоритетности классов, в которых они были определены.

Цели параметров :default-initargs и :initform различаются. Параметр класса :default-initargs позволяет пользователю указывать формы значения по-умолчанию для инициализационного аргумента без знания о том: для слота ли значение, или для метода. Если инициализационный аргумент не был указан в вызове make-instance, то используется его значение по-умолчанию и ситуация становится такой, как будто аргумент таки был указан. В отличие от этого, параметр слота :initform позволяет пользователю задать для слота значение по-умолчанию. Форма :initform используется только в случаях когда не было указано инициализационного аргумента в make-instance и для него в :default-initargs не было указано значение по-умолчанию.

Порядок вычисления форм значений по-умолчанию для инициализационных аргумент и порядок вычисления форм :initform не определены. Если порядок вычисления важен, используйте методы initialize-instance или shared-initialize.

Правила для инициализационных аргументов

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

Если в make-instance передано два или более инициализационных аргументов для одного и того же слота, тогда из них используется только тот, который стоит левее остальных, даже если имена аргументов отличаются.

Если два или более инициализационных аргументов задают значение для одного и того же слота, и ни один из них не указан в make-instance, тогда из них используется только тот, который встречается в параметре самого специфичного класса :default-initargs. Если один параметр класса :default-initargs определяет два или более инициализационных аргументов для одного и того же слота, используется значение наиболее левого из них, остальные игнорируются.

Инициализационные аргумент, явно указанные в make-instance, стоят слева от аргументов со значениями по-умолчанию. Предположим классы C1 и C2 содержат значения по-умолчанию для инициализационных аргумент для различных слотов, и предположим, что C1 более специфичен, чем C2. Тогда значения по-умолчанию из C1 в списке инициализационных аргументов будут стоять слева от значений из C2. Если один параметр класс :default-initargs содержит значения для нескольких различных слотов, в списке инициализационных аргументов эти значения будут указаны в таком же порядке.

Если слот имеет и форму :initform, и параметр слота :initarg и инициализационный аргумент имеет значение в :default-initargs или в make-instance, форма :initform не используется и не вычисляется.

Пример вышеназванных правил:

(defclass q () ((x :initarg a)))

(defclass r (q) ((x :initarg b))
  (:default-initargs a 1 b 2))

 Инициализация Содержимое
Форма  списка аргументовслота
(make-instance ’r)  (a 1 b 2) 1
(make-instance ’r ’a 3)  (a 3 b 2) 3
(make-instance ’r ’b 4)  (b 4 a 1) 4
(make-instance ’r ’a 1 ’a 2) (a 1 a 2 b 2) 1



 

shared-initialize

При создании, переинициализации, при обновлении до изменённого класса, обновлении до другого класса для заполнения слотов экземпляра класса с помощью инициализационных аргументов и форм :initform используется обобщённая функция shared-initialize. Она использует стандартный тип сочетания методов. Она принимает следующие аргументы: экземпляр для инициализации, спецификация набора слотов, доступных в данном экземпляре, и любое количество инициализационных аргументов. Аргументы после первых двух должны формировать список инициализационных аргументов.

Второй аргумент для shared-initialize может быть одним из:

В системе установлен главный метод для shared-initialize, у которого первый специализатор параметра является классом standard-object. Вне зависимости от того, разделяемый ли слот, или локальный, данный метод ведёт себя следующим образом:

Обобщённая функция shared-initialize вызывается из системных главных методов для обобщённых функций initialize-instance, reinitialize-instance, update-instance-for-different-class и update-instance-for-redefined-class. Таким образом для указания действий, которые должны быть выполнены во всех этих контекстах, можно написать методы для shared-initialize.

initialize-instance

Обобщённая функция initialize-instance вызывается из make-instance для инициализации вновь создаваемого экземпляра класса. Она используется стандартный тип сочетания методов. Для initialize-instance могут быть определены методы для выполнения какого-либо инициализационного алгоритма.

initialize-instance вызывается в процессе инициализации после выполнения следующих действий.

Вызывается обобщённая функция initialize-instance с параметрами: новый экземпляр, инициализационные аргументы. В системе для initialize-instance установлен главный метод, специализатором параметра которого является класс standard-object. Данный метод для заполнения слотов значениями в соответствие с инициализационными аргументами или формами :initform вызывает обобщённую функцию shared-initialize. Обобщённая функция shared-initialize вызывается со следующими аргументами: экземпляр, t и инициализационные аргументы.

Следует отметить, что initialize-instance в вызов shared-initialize предоставляет список инициализационных аргументов, таким образом первый шаг, выполняемый системным главным методом для shared-initialize учитывает и аргументы make-instance и список инициализационных аргументов.

Для указания действий при инициализации экземпляра могут быть определены методы для initialize-instance. Если для initialize-instance указаны только :after методы, они будут вызваны после системного метода и таким образом не будут влиять на поведение по-умолчанию.

Объектная система предоставляет две функции, которые весьма полезны в телах методов initialize-instance. Функция slot-boundp возвращает булево значение, которое показывает, имеет ли указанный слот значение. Это позволяет писать :after методы для initialize-instance, которые инициализируют слоты, только если они ещё не были инициализированы.

Определения make-instance and initialize-instance

Обобщённая функция make-instance ведёт себя так, как если была определена следующим образом (за исключением возможных оптимизаций):

(defmethod make-instance ((class standard-class) &rest initargs)
  (setq initargs (default-initargs class initargs))
  ...
  (let ((instance (apply #’allocate-instance class initargs)))
    (apply #’initialize-instance instance initargs)
    instance))

(defmethod make-instance ((class-name symbol) &rest initargs)
  (apply #’make-instance (find-class class-name) initargs))

Пропущенный код в определении make-instance проверяет переданные инициализационные аргументы на корректность. Эта проверка должна быть выполнена с помощью обобщённых функция class-prototype, compute-applicable-methods, function-keywords и class-slot-initargs. Смотрите третью часть спецификации для описания проверки инициализационных аргументов.

Обобщённая функция ведёт себя так, как если бы была определена следующим образом (за исключением возможных оптимизаций):

(defmethod initialize-instance
           ((instance standard-object) &rest initargs)
  (apply #’shared-initialize instance t initargs)))

Эти процедуры могут быть настроены (модифицированы) и на уровне функций из интерфейса к CLOS, и на метаобъектном уровне, или даже на всех двух уровнях.

Настройка на уровне CLOS включает использование параметров :initform, :initarg и :default-initargs в defclass, а также определение методов для make-instance и initialize-instance. Также возможно определение методов для shared-initialize, которые будут вызваны обобщёнными функциями reinitialize-instance, update-instance-for-redefined-class, update-instance-for-different-class и initialize-instance. Метаобъектный уровень поддерживает дополнительные настройки с помощью определения методов для make-instance, default-initargs и allocate-instance.

Реализации могут оптимизировать initialize-instance и shared-instance. Описание shared-initialize в разделе 28.2 содержит возможные оптимизации.

Из-за оптимизаций, проверка корректности инициализационных аргументов может не использовать обобщённые функции class-prototype, compute-applicable-methods, function-keywords и class-slot-initargs Кроме того, методы обобщённой функции default-initargs и системные главные методы для allocate-instance, initialize-instance и shared-initialize могут вызываться не каждый раз при выполнении make-instance или могут получать не всех аргументы, которые хотят.

28.1.10 Переопределение классов

Класс, который является экземпляром standard-class может быть переопределён, если новый экземпляр также принадлежит классу standard-class. Переопределение класса — это модификация существующего объекта класса для соответствия с новым определением. Это не создание нового объекта класса. Любые объекты методов созданные в соответствие с параметрами :reader, :writer или :accessor в старом определении класса удаляются из соответствующих обобщённых функций. А методы указанные в новом определении класса наоборот добавляются.

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

Следует отметить, что переопределение класса может приводить к добавлению или удалению слотов. Если при переопределении класса произошло изменения набора слотов для экземпляров, то последние будут обновлены. Если же локальные слоты изменены не были, процесс обновления экземпляров зависит от реализации.

Значение слота, который и в старом, и в новом классах является разделяемым, остаётся неизменным. Если он был несвязанным, он таким же и останется. Слот, который в старом классе был локальным, а в новом стал разделяемым, будет заново инициализирован. Вновь добавленные разделяемые слоты также будут инициализированы.

Каждый вновь добавленный разделяемый слот будет иметь значение формы :initform, определённой в новом классе. Если такой формы не было, слот остаётся несвязанным.

Если происходит переопределение класса и при этом изменяется набор локальных слотов, то обновление всех экземпляров происходит в два шага. Процесс может быть запущен явно с помощью обобщённой функции make-instance-obsolete. В некоторых реализациях этот процесс может быть запущен и при других обстоятельствах. Например, при изменении порядка слотов.

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

Изменение структуры экземпляров классов

Первый шаг изменяет структуру экземпляров переопределяемого класса для согласования с новым определением. Добавляются локальные слоты, указанные в новом классе, но отсутствующие или бывшие разделяемыми в определении старого. Удаляются слоты (и локальные, и разделяемые), не существующие в определении нового класса, но бывший в определении старого. Имена этих добавленных и удалённых слотов передаются в update-instance-for-redefined-class как написано в следующем разделе.

Значений локальных слотов, указанных и в новом, и в старом определениях, остаются нетронутыми. Если слот был несвязанным, он таким и остаётся.

Значения слотов, которые в старом определении были разделяемыми, а в новом стали локальными, остаются нетронутыми. Если такой слот был несвязанным, он таким и остаётся.

Инициализация вновь добавленных локальных слотов

Второй шаг обновления инициализируется вновь добавленные слоты и выполняет другие пользовательские действия. Данный шаг реализован обобщённой функцией update-instance-for-redefined-class, которая вызывается после того как завершается первый шаг обновления или было произведено изменение структуры класса.

Обобщённая функция update-instance-for-redefined-class принимает четыре обязательных аргумента: обновляемый экземпляр после первого шага, список имён локальных слотов, которые были добавлены, список имён локальный слотов, которые были удалены, и список свойств, содержащий имена слотов и из значения, которые были удалены и имели эти значения. В удалённые слоты включаются также те слоты, которые в старом классе были локальными, а в новом — разделяемыми.

Обобщённая функция update-instance-for-redefined-class также принимает любое количество инициализационных аргументов. Когда система вызывает данную функцию для обновления экземпляра, класс которого был переопределён, ни одного такого аргумента не указывается.

В системе установлен главный метод для update-instance-for-redefined-class, у которого специализатор параметра является классом standard-object. Сначала этот метод проверяет корректность инициализационных аргументов и сигнализирует ошибку в случае неудачи. Смотрите раздел 28.1.9. Затем он вызывает обобщённую функцию shared-initialize со следующими аргументами: экземпляр класса, список имён вновь добавленных слотов и принятые инициализационные аргументы.

Настройка переопределения класса

Для указания действий, происходящих при обновлении экземпляра класса, могут быть определены методы для update-instance-for-redefined-class. Если для update-instance-for-redefined-class определены только :around методы, они будут запущены после системного главного метода и не будут влиять на поведение по-умолчанию update-instance-for-redefined-class. Так как в update-instance-for-different-class системой не передаются инициализационные аргументы, формы :initform для слотов, которые были заполнены в :before методах, для update-instance-for-redefined-class вычисляться не будут.

Для настройки переопределения класса могут быть определены методы для shared-initialize (смотрите раздел 28.1.9).

Расширения

Существует для расширения для переопределения класса:

28.1.11 Изменение класса для некоторого экземпляра

Функция change-class может использоваться для изменения класс некоторого экземпляра с текущего Cfrom на какой-либо другой Cto. Функция изменяет структуру экземпляра для согласования с определением класса Cto.

Следует отметить, что изменение класса экземпляра может вызвать добавление или удаление слотов.

При вызове change-class для некоторого экземпляра, происходят два шага его обновления. Первый шаг модифицирует структуру экземпляра, а, именно, добавляет новые локальные и/или удаляет старые локальные слоты, которые не были определены в новом классе. Второй шаг инициализирует добавленные локальные слоты и выполняет некоторые действия, определённые пользователем. Эти шаги описаны в следующих разделах.

Модификация структуры экземпляра

Для того, согласовать экземпляр с классом Cto, добавляются локальные слоты, указанные в классе Cto, но не указанные в Cfrom, и удаляются локальные слоты не определённые в Cto, но определённые в Cfrom.

Значения локальных слотов указанные и в классе Cto и в классе Cfrom сохраняются как есть. Если такой локальный слот не связан с каким-либо значение, он остаётся несвязанным.

Значения слотов, определённых в классе Cfrom как разделяемые, и в классе Cto как локальные, остаются неизменными.

Данный первый шаг обновления экземпляра не затрагивает значения каких-либо разделяемых слотов.

Инициализация вновь добавленных локальных слотов

Второй шаг обновления инициализируется вновь добавленные слоты и выполняет другие пользовательские действия. Данный шаг реализован обобщённой функцией update-instance-for-different-class. Обобщённая функция update-instance-for-different-class вызывается функцией change-class, после того как завершается первый шаг обновления.

Обобщённая функция update-instance-for-different-class вызывается с двумя аргументами, вычисленными в change-class. Первый передаваемый аргумент — копия обновляемого экземпляра, который принадлежит классу Cfrom. Данная копия имеет динамическую продолжительность видимости внутри обобщённой функции change-class. Второй аргумент — обновляемый экземпляр класса Ct.

Обобщённая функция update-instance-for-different-class также принимает любое количество инициализационных аргументов. При вызове из change-class таких аргументов ни одного не указывается.

В системе установлен главный метод для update-instance-for-different-class, у которого есть два специализатора параметров, каждый из которых является классом standard-object. Сначала этот метод проверяет корректность инициализационных аргументов и сигнализирует ошибку в случае неудачи. Смотрите раздел 28.1.9. Затем он вызывает обобщённую функцию shared-initialize со следующими аргументами: экземпляр класса, список имён вновь добавленных слотов и принятые инициализационные аргументы.

Настройка изменения класса экземпляра

Для указания действий, происходящих при обновлении экземпляра класса, могут быть определены методы для update-instance-for-different-class. Если для update-instance-for-different-class определены только :around методы, они будут запущены после системного главного метода и не будут влиять на поведение по-умолчанию update-instance-for-different-class. Так как функция change-class не передаёт в update-instance-for-different-class инициализационные аргументы, формы :initform для слотов, которые были заполнены в :before методах, для update-instance-for-different-class вычисляться не будут.

Для настройки переопределения класса могут быть определены методы для shared-initialize (смотрите раздел 28.1.9).

28.1.12 Переинициализация экземпляра

Для изменения значений слотов в соответствии с инициализационными аргументами может использоваться обобщённая функция reinitialize-instance.

Процесс переинициализации изменяет значения некоторых слотов и выполняет определённые пользователем действия.

Переинициализация не модифицирует структуру экземпляра класса, и не используется какие-либо формы :initform для инициализации слотов.

Обобщённая функция reinitialize-instance может быть вызвана напрямую. Она принимает один обязательный аргумент, экземпляр класса. Она также принимает любое количество инициализационных аргументов для их использования в методов reinitialize-instance или для shared-initialize. Аргументы после обязательного экземпляра класса, должны формировать инициализирующий список аргументов.

В системе установлен главный метод для reinitialize-instance, у которого специализатором параметра является standard-object. Сначала этот метод проверяет корректность инициализационных аргументов и в случае некорректности сигнализирует ошибку (смотрите раздел 28.1.9). Затем он вызывает обобщённую функцию shared-initialize со следующими аргументами: экземпляр класса, nil, и инициализационные аргументы.

Настройка переинициализации

Для указания действий при обновлении экземпляра класса могут быть определены методы для обобщённой функции reinitialize-instance. Если определены только :after методы, они будут выполнены после установленного системой главного метода и таким образом не будут изменять стандартного поведения reinitialize-instance.

Для настройки переопределения класса могут быть определены методы для shared-initialize. Смотрите раздел 28.1.9.