有没有办法访问CLOS中超类列表中的插槽?

时间:2013-10-30 00:25:38

标签: common-lisp clos mop

有没有办法在CLOS中访问超类的插槽?

例如,在目标C中我可以执行

- (void) frob {
[super frob]
}

这会向frob的(唯一)超类发送消息。

仔细阅读CLOS文档表明DEFCLASS合并了有关类创建的所有超类信息,因此这种与超类通信的能力将丢失。它是否正确?

编辑:

这种情况有点不寻常:

给定课程

(defclass animal ()
  ((behavior-types
     :initform '(:eat :sleep :drink)
     :reader behavior-types)))

(defclass cow (animal)  
  ((behavior-types
     :initform '(:moo :make-milk)
     :reader behavior-types))

(defclass horse
  ((behavior-types 
     :initform '(:buck :gambol :neigh)
     :reader behavior-types))

如何使用BEHAVIOR-TYPESGET-BEHAVIOR方法,当使用horse类型的对象调用时,返回'(:eat :sleep :drink :buck :gambol :neigh)。也就是说,通过槽的继承“添加”到initform而不是替换它。

一个简单的解决方案是,而不是将数据分配给类,以获得如下通用方法:

(defgeneric behavior-types (obj))

(defmethod behavior-types ((obj animal)) nil)

(defmethod behavior-types :around ((obj animal))
  (append '(:eat :sleep :drink)
          (call-next-method obj)))


(defmethod behavior-types :around ((obj horse))
  (append '(:gambol :neigh :buck)
          (call-next-method obj)))

但是,此解决方案将数据移动到defgeneric而不是正确属于的类。因此问题的动机来自于此。

无论如何 - 提出的问题反映了对CLOS设计的误解。根据要求并且在普通框架内,执行此任务是不可能的。但是,下面给出了两种不同的方法,使用MOP来解决我提出的问题。

3 个答案:

答案 0 :(得分:5)

你的问题的标题听起来好像你在询问如何访问插槽,但你展示的代码看起来更像是调用专门用于超类的方法。如果您正在寻找后者,则应该查看call-next-method以及HyperSpec中的7.6 Generic Functions and Methods

调用“超类方法”

在CLOS中,方法不像其他语言那样属于类。相反,存在定义专用方法的泛型函数。对于给定的参数列表,可能适用多种方法,但只有一种方法是最具体的。您可以使用call-next-method调用下一个最具体的方法。在下面的记录中,有一个类FOO和一个子类BAR,以及一个通用函数FROB,它具有专门用于FOOBAR的方法。在专门针对BAR的方法中,调用call-next-method,在这种情况下,调用专用于FOO的方法。

CL-USER> (defclass foo () ())
;=> #<STANDARD-CLASS FOO>
CL-USER> (defclass bar (foo) ())
;=> #<STANDARD-CLASS BAR>
CL-USER> (defgeneric frob (thing))
;=> #<STANDARD-GENERIC-FUNCTION FROB (0)>
CL-USER> (defmethod frob ((foo foo))
           (print 'frobbing-a-foo))
;=> #<STANDARD-METHOD FROB (FOO) {1002DA1E11}>
CL-USER> (defmethod frob ((bar bar))
           (call-next-method)
           (print 'frobbing-a-bar))
;=> #<STANDARD-METHOD FROB (BAR) {1002AA9C91}>
CL-USER> (frob (make-instance 'bar))

FROBBING-A-FOO 
FROBBING-A-BAR 
;=> FROBBING-A-BAR

使用方法组合

进行模拟

您可以使用方法组合来组合适用于参数列表的方法的结果。例如,您可以使用方法组合a定义方法list,这意味着当您致电(a thing)时,所有适用于a的方法调用参数,并将它们的结果合并到一个列表中。如果您在不同的类中为插槽提供不同的名称,并在a上专门设计读取这些值的方法,则可以模拟您正在寻找的类型。这并不妨碍您也使用访问插槽的传统阅读器(例如,在以下示例中为get-a)。以下代码显示了一个示例:

(defgeneric a (thing)
  (:method-combination list))

(defclass animal ()
  ((animal-a :initform 'a :reader get-a)))

(defmethod a list ((thing animal))
  (slot-value thing 'animal-a))

(defclass dog (animal)
  ((dog-a :initform 'b :reader get-a)))

(defmethod a list ((thing dog))
  (slot-value thing 'dog-a))

(a (make-instance 'dog))

(get-a (make-instance 'animal))
;=> A

(get-a (make-instance 'dog))
;=> B

使用MOP

从1998年开始,

This post关于Allegro CL档案值得一读。听起来好像作者正在寻找类似于你所寻找的东西。

  

我需要定义一个连接的继承行为   具有本地槽初始化的超类初始化的字符串值。 E.g。

(defclass super()
  ((f :accessor f :initform "head")) (:metaclass user-class))

(defclass sub(super)
  ((f :accessor f :initform "tail")) (:metaclass user-class))
     

我想得到以下内容:

(f(make-instance'sub)) -> "head tail"
     

我没有在defclass插槽描述中找到标准选项   这个。我想为每个组合定义连接组合   元级“用户级”。

回应(由Heiko Kirschke,不是我,但也看到this response from Jon White采用类似方法),定义了一种新类型:

(defclass user-class (standard-class) ())

并专门提供clos:compute-effective-slot-definition以提供根据类及其超类的槽定义计算的initform:

(defmethod clos:compute-effective-slot-definition
    ((the-class user-class) slot-name
     ;; The order of the direct slots in direct-slot-definitions may
     ;; be reversed in other LISPs (this is code written & tested with
     ;; ACL 4.3):
     direct-slot-definitions)
  (let ((slot-definition (call-next-method))
    (new-initform nil))
    (loop for slot in direct-slot-definitions
    as initform = (clos:slot-definition-initform slot)
    when (stringp initform)
    do
      ;; Collecting the result string could be done perhaps more
      ;; elegant:
      (setf new-initform (if new-initform
                 (concatenate 'string initform " "
                          new-initform)
                   initform)))
    (when new-initform
      ;; Since at (call-next-method) both the initform and
      ;; initfunction of the effective-slot had been set, both must be
      ;; changed here, too:
      (setf (slot-value slot-definition 'clos::initform) new-initform)
      (setf (slot-value slot-definition 'clos::initfunction)
    (constantly new-initform)))
    slot-definition))

然后就像这样使用:

(defclass super ()
  ((f :accessor f :initform "head"))
  (:metaclass user-class))

(defclass sub(super)
  ((f :accessor f :initform "tail"))
  (:metaclass user-class))

(f (make-instance 'sub))
==> "head tail"

这是进入规范未指定的MOP功能,因此您可能必须根据您的特定实现进行调整。但是,有一些MOP兼容层包可能会帮助你。

答案 1 :(得分:3)

CLOS中没有超类的实例槽这样的概念。

如果您创建实例,则它包含所有插槽。来自班级及其超类的所有插槽。

如果一个类有一个插槽FOO,而某些超类也有一个名为FOO的插槽,那么所有这些插槽都会合并到一个插槽中。该CLOS类的每个实例都将具有该插槽。

你还需要更加小心你的措辞。超类本身就是对象,它们本身也有插槽。但这与具有本地插槽并具有带实例插槽的超类的实例无关。后者在CLOS中不存在。

CL-USER 18 > (defclass bar () (a b))
#<STANDARD-CLASS BAR 413039BD0B>

然后,上面是一个有两个插槽的超类。

CL-USER 19 > (defclass foo (bar) (b c))
#<STANDARD-CLASS FOO 4130387C93>

上面是一个有两个本地和一个继承槽的类。插槽b实际上是从这个类和超类中合并的。

CL-USER 20 > (describe (make-instance 'foo))

#<FOO 402000951B> is a FOO
B      #<unbound slot>
C      #<unbound slot>
A      #<unbound slot>

上面显示该实例有三个插槽,所有插槽都可以直接访问。甚至是插槽`a,它是在超类中定义的。

如果我们将实际的超类看作一个实例本身,我们会看到它的插槽:

CL-USER 21 > (describe (find-class 'bar))

#<STANDARD-CLASS BAR 413039BD0B> is a STANDARD-CLASS
NAME                         BAR
DEFAULT-INITARGS             NIL
DIRECT-DEFAULT-INITARGS      NIL
DIRECT-SLOTS                 (#<STANDARD-DIRECT-SLOT-DEFINITION A 4020005A23> #<STANDARD-DIRECT-SLOT-DEFINITION B 4020005A93>)
DIRECT-SUBCLASSES            (#<STANDARD-CLASS FOO 4130387C93>)
DIRECT-SUPERCLASSES          (#<STANDARD-CLASS STANDARD-OBJECT 40F017732B>)
PRECEDENCE-LIST              (#<STANDARD-CLASS BAR 413039BD0B> #<STANDARD-CLASS STANDARD-OBJECT 40F017732B> #<BUILT-IN-CLASS T 40F00394DB>)
PROTOTYPE                    NIL
DIRECT-METHODS               NIL
WRAPPER                      #(1539 (A B) NIL #<STANDARD-CLASS BAR 413039BD0B> (#<STANDARD-EFFECTIVE-SLOT-DEFINITION A 4020005AFB> #<STANDARD-EFFECTIVE-SLOT-DEFINITION B 4020005B63>) 2)
LOCK                         #<MP::SHARING-LOCK "Lock for (STANDARD-CLASS BAR)" Unlocked 41303AD4E3>
DOCUMENTATION-SLOT           NIL
PLIST                        (CLOS::COPYABLE-INSTANCE #<BAR 402000638B>)
POTENTIAL-INITARGS           0
MAKE-INSTANCE-FLAGS          509
OTHER-LOCK                   #<MP:LOCK "Lock for (OTHER STANDARD-CLASS BAR)" Unlocked 41303AD553>
REINITIALIZE-INITARGS        0
REDEFINE-INITARGS            0
DEPENDENTS                   NIL

答案 2 :(得分:1)

这真的非常非常糟糕。我希望有人会介入并解决它,尽管它应该说明这个想法:

(defclass agent () ((behaviour :initform do-nothing :accessor behaviour-of)))

(defclass walk-agent (agent) ((behaviour :initform and-walk)))

(defclass talk-agent (walk-agent) ((behaviour :initform and-talk)))

(defmethod sb-mop:compute-effective-slot-definition
           :after (class (name (eql 'behaviour)) sdlotds)
  (setf *slot-def* 
        (loop
           :for slot :in sdlotds :do
           (format t "~&slot: ~s" (sb-mop:slot-definition-initform slot))
           :collect (sb-mop:slot-definition-initform slot))))

(defmethod initialize-instance :before ((instance agent) &rest keyargs)
  (declare (ignore keyargs))
  (let (*slot-def*)
    (declare (special *slot-def*))
    (sb-mop:compute-slots (class-of instance))
    (setf (behaviour-of instance) *slot-def*)))

;; (behaviour-of (make-instance 'talk-agent))

;; slot: AND-TALK
;; slot: AND-WALK
;; slot: DO-NOTHING
;; slot: AND-TALK
;; slot: AND-WALK
;; slot: DO-NOTHING
;; (AND-TALK AND-WALK DO-NOTHING)

PS。我看到计算SBCL中的槽定义列表的函数是std-class.lisp,std-compute-slots。所以这不是MOP以某种方式定义的......但是这个在这里真的很有用。