在lisp中将对象作为自身的参数

时间:2018-06-12 22:24:18

标签: oop lisp common-lisp clos object-initialization

在Python中,我会这样做:

class foo:
    def __init__(self):
        self.x = self

否则,现在该对象是其自身的参数。我怎么能在普通的lisp中做到这一点?

(defclass mn ()
  ((pai   :accessor mn-pai
          :initarg :pai
          :initform self)))

3 个答案:

答案 0 :(得分:6)

DEFCLASS 广告位说明中,无法引用对象本身。但是可以为实例初始化编写方法。这与您的Python示例类似。

我们班:

? (defclass foo ()
    ((bar :accessor foo-bar :initarg :foo)))
#<STANDARD-CLASS FOO>

我们为:after创建initialize-instance方法。此通用函数由CLOS提供,其目的是初始化新实例。第一个参数是要初始化的实例。当我们创建类foo的实例时,Lisp系统将调用该方法。

使用访问者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已设置为实例本身。

答案 1 :(得分:2)

请注意initformdefclass的词汇上下文中进行评估,但是make-instance的动态上下文。这允许您定义一个名为*this*的特殊变量(您可以使用this,但这可能会令人困惑)并在初始化对象时使用它。

(defvar *this*)

为可能引用*this*的类定义mixin:

(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}>

答案 2 :(得分:-1)

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))

那么,在基于普通类的系统中,你会把这个方法放在哪里?在Utility类中?这是一个“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-mnmn2的一个实例,因为我们告诉系统它必须在我们对参数进行限定时。

但是,再次,您可以看到在基于类的系统中,这种方法没有“地方”。我们通常只创建某种类型的Utility类并将其粘贴在那里,或者在全局命名空间中使用常规函数。

虽然CLOS有类,但它并不是真正的基于类的系统。

这也出现在多继承场景中(CLOS支持)。那么谁是“自我”?