在 Python 中,我会这样做:
class foo:
def __init__(self):
self.x = self
否则,现在对象是它自己的参数。我怎样才能用普通的 lisp 做到这一点?
(defclass mn ()
((pai :accessor mn-pai
:initarg :pai
:initform self)))
在 Python 中,我会这样做:
class foo:
def __init__(self):
self.x = self
否则,现在对象是它自己的参数。我怎样才能用普通的 lisp 做到这一点?
(defclass mn ()
((pai :accessor mn-pai
:initarg :pai
:initform self)))
在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*)
为可能引用的类定义一个 mixin *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 没有“this”或“self”的概念,因为通过使用泛型函数,任何正在执行的实例都作为参数传递。
因此,给定您使用访问器的示例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
因为我们告诉系统它必须是当我们限定参数时。
但是,再一次,您可以看到在基于类的系统中,这种方法没有“位置”。我们通常只是创建某种实用程序类并将它们粘贴在那里,或者在全局命名空间中放置一个常规函数。
虽然 CLOS 有类,但它并不是真正的基于类的系统。
这也出现在多继承场景(CLOS 支持)中。那么谁是“自我”呢?