【问题标题】:Object as a parameter of itself in lisp对象作为 lisp 中自身的参数
【发布时间】:2018-06-12 22:24:18
【问题描述】:

在 Python 中,我会这样做:

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

否则,现在对象是它自己的参数。我如何在 common lisp 中做到这一点?

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

【问题讨论】:

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


    【解决方案1】:

    DEFCLASS 槽描述中不能引用对象本身。但是可以为实例初始化编写方法。这将类似于您的 Python 示例。

    我们班:

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

    我们为initialize-instance 创建了一个:after 方法。这个通用函数由 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 已设置为实例本身。

    【讨论】:

      【解决方案2】:

      请注意,initform 是在 defclass 的词汇上下文中计算的,但在 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}>
      

      【讨论】:

        【解决方案3】:

        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-mnmn2 的一个实例,因为我们告诉系统它必须是当我们限定参数时。

        但是,同样,您可以看到在基于类的系统中,这种方法没有“位置”。我们通常只创建某种实用程序类并将它们粘贴在其中,或者在全局命名空间中添加一个常规函数。

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

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

        【讨论】:

        • CLOS 中的运行时调度不是基于类型,而是基于类。调度参数的顺序错误。它也不“知道”一个对象的类,因为有人告诉它,而是因为对象携带它的类。对于inc-both,无论是否调度类都没有区别。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多