有没有办法在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-TYPES
或GET-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来解决我提出的问题。
答案 0 :(得分:5)
你的问题的标题听起来好像你在询问如何访问插槽,但你展示的代码看起来更像是调用专门用于超类的方法。如果您正在寻找后者,则应该查看call-next-method
以及HyperSpec中的7.6 Generic Functions and Methods。
在CLOS中,方法不像其他语言那样属于类。相反,存在定义专用方法的泛型函数。对于给定的参数列表,可能适用多种方法,但只有一种方法是最具体的。您可以使用call-next-method
调用下一个最具体的方法。在下面的记录中,有一个类FOO
和一个子类BAR
,以及一个通用函数FROB
,它具有专门用于FOO
和BAR
的方法。在专门针对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
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以某种方式定义的......但是这个在这里真的很有用。