2

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

例如,在 Objective CI 中可以执行

- (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-TYPES或者GET-BEHAVIOR当使用 type 的对象调用时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 来解决我提出的问题。

4

3 回答 3

5

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

调用“超类方法”</h2>

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

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

使用拖把

这篇1998 年关于 Allegro CL 档案的帖子值得一读。听起来作者正在寻找与您正在寻找的东西相似的东西。

我需要定义一个继承行为,将超类初始化形式的字符串值与本地插槽初始化形式连接起来。例如

(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 slot-descriptions 中找到一个标准选项。我想为每个元类“用户类”定义连接组合。

响应(来自 Heiko Kirschke,不是我,但也可以看到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 兼容层包可能会帮助您。

于 2013-10-30T00:53:22.317 回答
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>

如上图,实例有3个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
于 2013-10-30T23:17:39.163 回答
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 以某种方式定义的东西......但是这个在这里真的很有帮助。

于 2013-10-30T07:33:05.797 回答