Объект как параметр самого себя в lisp
В Python я бы сделал так:
class foo:
def __init__(self):
self.x = self
В противном случае, теперь объект является параметром самого себя. Как я могу сделать это в общих чертах?
(defclass mn ()
((pai :accessor mn-pai
:initarg :pai
:initform self)))
3 ответа
В DEFCLASS
описание слота нельзя ссылаться на сам объект. Но можно написать методы для инициализации экземпляра. Это будет похоже на ваш пример Python.
Наш класс:
? (defclass foo ()
((bar :accessor foo-bar :initarg :foo)))
#<STANDARD-CLASS FOO>
Мы создаем :after
метод для initialize-instance
, Эта универсальная функция предоставляется CLOS, и ее целью является инициализация нового экземпляра. Первый аргумент - это экземпляр для инициализации. Метод будет вызван системой Lisp, когда мы создадим экземпляр класса foo
,
Использование аксессора foo-bar
:
? (defmethod initialize-instance :after ((object foo) &key)
(setf (foo-bar object) object))
#<STANDARD-METHOD INITIALIZE-INSTANCE :AFTER (FOO)>
или настройка слота через (setf slot-value)
,
? (defmethod initialize-instance :after ((object foo) &key)
(setf (slot-value object 'bar) object))
#<STANDARD-METHOD INITIALIZE-INSTANCE :AFTER (FOO)>
Обратите внимание, что мы можем назвать параметр экземпляра любым именем: object
или даже self
, Но у названия нет семантики. Так как в CLOS у нас есть мультидиспетчер (диспетчеризация может работать с более чем одним аргументом и нет отправленного аргумента по умолчанию), нет self
семантика.
Теперь мы делаем и описываем экземпляр класса foo
:
? (describe (make-instance 'foo))
#<FOO #x302000D20C0D>
Class: #<STANDARD-CLASS FOO>
Wrapper: #<CCL::CLASS-WRAPPER FOO #x302000D2B43D>
Instance slots
BAR: #<FOO #x302000D20C0D>
Как видите, слот bar
этого экземпляра был установлен на сам экземпляр.
Обратите внимание, что initform
оценивается в лексическом контексте defclass
, но динамический контекст make-instance
, Это позволяет вам определить специальную переменную с именем *this*
(вы могли бы использовать this
, но это может сбивать с толку) и использовать его при инициализации объектов.
(defvar *this*)
Определите миксин для классов, которые могут ссылаться *this*
:
(defclass knows-this () ())
(defmethod shared-initialize :around ((object knows-this) slot-names &rest args)
(declare (ignore args))
(let ((*this* object))
(call-next-method)))
Например:
(defclass foo (knows-this)
((myself :initform *this*)))
(describe (make-instance 'foo))
#<FOO {100AC6EF13}>
[standard-object]
Slots with :INSTANCE allocation:
MYSELF = #<FOO {100AC6EF13}>
CLOS не имеет понятия "это" или "я", потому что с помощью универсальных функций любой экземпляр, на который выполняется действие, передается в качестве аргумента.
Итак, учитывая ваш пример с аксессором mn-pai
:
(setf instance (make-instance 'mn))
(mn-pai instance 1)
Вот, instance
передается в качестве аргумента методу доступа.
Если вы создали метод:
(defmethod inc-pai (an-mn amount)
(incf (mn-pai an-mn) amount))
Опять же, вы видите, что экземпляр передается в качестве первого аргумента. Таким образом, всегда есть явный аргумент, который вы используете.
Теперь рассмотрим:
(defmethod inc-both (an-mn another-mn amount)
(incf (mn-pai an-mn) amount)
(incf (mn-pai another-mn) amount))
Итак, в обычной системе, основанной на классах, где бы вы поместили этот метод? В служебном классе? Это метод класса "MN"? Это не поддается готовой категоризации.
Теперь рассмотрим:
(defclass mn2 ()
((pai :accessor mn2-pai)))
если бы мы должны были сделать это:
(setf an-mn (make-instance 'mn))
(setf an-mn2 (make-instance 'mn2))
(inc-both an-mn an-mn2)
Вторая строка потерпит неудачу, так как у mn2 нет mn-pai
сбруя.
Тем не менее, это будет работать:
(defmethod inc-both2 (an-mn another-mn amount)
(incf (slot-value 'pai an-mn) amount)
(incf (slot-value 'pai another-mn) amount))
Поскольку slot-value
является примитивным аксессором для CLOS, и оба класса имеют слот с именем pai
, Но тогда вы не можете вызвать функцию доступа. Скорее вы устанавливаете слот напрямую. Наверное, не то, что вы хотите. И, конечно же, имена являются совпадением. Нет никакой связи между классами, кроме их похожих имен и имени общего слота.
Но тогда вы можете сделать это:
(defmethod inc-both ((mn an-mn) (mn2 another-mn) amount)
(incf (mn-pai an-mn) amount)
(incf (mn-pai2 another-mn) amount))
Это работает, потому что среда выполнения будет отправлять в зависимости от типов параметров. Мы знаем" another-mn
это пример mn2
потому что мы сказали системе, что это должно быть, когда мы уточнили аргумент.
Но, опять же, вы можете видеть, как в системе на основе классов нет "места" для такого метода. Обычно мы просто создаем некоторый класс Utility и добавляем его туда, или обычную функцию в глобальном пространстве имен.
Хотя в CLOS есть классы, на самом деле это не система, основанная на классах.
Это также встречается в сценариях с множественным наследованием (которые поддерживает CLOS). Кто тогда "я"?