I am learning to structure my CL programm and now having trouble to use the CLOS while programming in the large with packages.
package.lisp
(defpackage :my-project.a
(:use :cl)
(:export
create-my-object
my-object
; EXPORT SINGLE SLOTS?
my-slot-1
; my-slot-...
; my-slot-n
; OR EXPORT ALL ACCESSOR-FUNCTIONS?
my-slot-1-accessor
; my-slot-n-accessor...
))
(defpackage :my-project.b
(:use :cl :my-project.a)
(:export print-object-slot))
src.lisp
While the class MY-OBJECT is defined in MY-PROJECT.A
(in-package :my-project.a)
(defclass my-object ()
((my-slot-1 :accessor my-slot-1-accessor :initarg :my-slot-1)
;... more slots
; (my-slot-2 :accessor my-slot-2-accessor :initarg :my-slot-2)
; (my-slot-n :accessor my-slot-n-accessor :initarg :my-slot-n)
))
as some CREATOR function for the objects
(defun create-my-object ()
(make-instance 'my-object
:my-slot-1 "string"
;; further slots...
))
Having some function e.g. PRINT-OBJECT in the package MY-PROJECT.B,
which should handle the object instanciated from a function
(in-package :my-project.b)
(defun print-object-slot (slot-name object)
(format nil "slot-value: ~a" (SLOT-VALUE object slot-name)))
Problem
While executing following code doesn't work
(in-package :my-project.b)
(describe 'my-object) ; works
(print-object-slot
'my-slot-1 ; while this works: 'my-project.a:my-slot-1 [if slot is exported]
(create-my-object))
;; ==> slot MY-PROJECT.B:MY-SLOT-1 is missing from the object
;; MY-PROJECT.A:MY-OBJECT
To access my slots programmatically, in this situation I would need to merge the originating package-name with the slot-name, to get/setf the slot from external classes...
My understanding
The accessor-functions from CLOS objects are generic functions, belonging to the package, where they have been defined via DEFCLASS, in this case: MY-PROJECT.A
By (use-package :my-project.a) in MY-PROJECT.B, the exported symbols are imported, that's why DESCRIBE works. But the symbols of the generic-slot-accessor-functions aren't included.
Consideration:
The architecture of the programm should NOT be planned to share/export objects and slot-access. It's not well designed to bulk-import/export slots/accessor-functions.
Consideration:
You can build a custom function, which get/sets the slots via the slot-accessor-function inside their package, so there is just one interface function to export?
My question:
This way handling external CLOS objects doesnt seem to be the way to go.
How to export/import those accessor-functions in a sane way, without listing manually every single slot?
Edit/Solution
My terminolgy and use of slots vs. accessor-functions is a cause of this problem (thank you so much #RainerJoswig for clearing terminology up).
I did'nt use an exported version of MY-SLOT-1-ACCESSOR function, which would work as expected, but would need my to "bulk-export" them, if I would like to have access all slots in every other external package. #sds did a great job to show how to do this, and also at pointing out the general problem of my approach. Many thanks :)
In my mind, I wished to export just the object and gain full access to all the internal functions. But that's the wrong way for CLOS, since symbols and methods don't share direct bindings to the class/object, and I have to adapt better organisation of code.
Terminology
The question does not make the differences between a slot, slot-name and a slot accessor function clear. Conflating slot names and accessor functions is not that a good idea. You should be clear what is what.
(defpackage "GUI"
(:use "CL")
(:export
;; class
window
window-screen
window-width
window-height))
(defclass window ()
((screen :accessor window-screen :initarg :screen)
(width :accessor window-width :initarg :width :initform 640)
(height :accessor window-height :initarg :height :initform 400)))
Now screen is a slot name and window-screen is an accessor function.
The slot name is just a symbol. You can use any symbol for that. For example you can also write (just a random example, don't use):
(defpackage "SLOTS" (:use))
(defpackage "AC" (:use)
(:export
"WINDOW-SCREEN"
"WINDOW-WIDTH"
"WINDOW-HEIGHT"))
(defclass window ()
((slots::screen :accessor ac:window-screen :initarg :screen)
(slots::width :accessor ac:window-width :initarg :width :initform 640)
(slots::height :accessor ac:window-height :initarg :height :initform 400)))
Above would use slot names in a package slots and accessors in a package ac.
An accessor is a generic function.
So, when you write:
(defun foo (instance slot-name)
...)
I would expect that slot-name is a symbol, not an accessor function.
(defun foo (instance accessor)
...)
For above I would expect accessor to be a function, not a symbol.
If you really want to make the difference clear, you can write methods:
(defmethod foo (instance (path symbol))
(slot-value instance path))
(defmethod foo (instance (path function))
(funcall function instance))
What to export?
Usually I would export accessor names in a package, but not slot names.
Import?
But often I would not even import the package:
(defpackage "GUI-GAME"
(:use "CL"))
Above package does not import package gui. It could, but here it doesn't.
(defmethod describe-window ((w gui:window))
(format t "~% Window width:~a height:~a"
(gui:window-width w)
(gui:window-width h)))
The advantage is that I see two things in the source code:
gui:window is exported and thus part of a package interface
gui:window is actually from the package gui and there is no name
conflict with other symbols.
Just use the symbols for the class and the accessor functions with their package names prepended.
Exporting all accessors
You can use MOP to get the list of readers and
writers for your class and then export all of them, using
find-class
class-direct-slots
slot-definition-readers
export
like this:
(dolist (slot (class-direct-slots (find-class 'your-class-name)))
(dolist (reader (slot-definition-readers slot))
(export reader)))
Why is it so complicated?
Because you do not want to do that.
All code which needs indiscriminate access to all slots of a class
should be in the same package as the class.
The only symbols you export should be those you need to export, and
they should be explicitly vetted by you.
Your print-object-slot function is trying to call a function named literaly named slot-name, not the function named by the variable slot-name. You want to use funcall here.
(defun print-object-slot (slot-name object)
(format nil "slot-value: ~a" (funcall slot-name object)))
Related
(defpackage #:my-test-package
(:use #:common-lisp
#:my-test-runner)
(:import-from #:my-package
#:name
#:path
#:system-path
#:something
#:more-something
#:and-more-something))
The problem is that the :my-test-package also use :name and :path by example, and I need of both, so I'm import all that I need, but it's boring remember this all the time, and also I need test things that aren't exported in the my-package and I don't want use my-package::<something> all the time, so, how to can I a loop in all (exported or not) keys of my-package in the :import-from #:my-package <...> instead of needing import manually?
If you want to simply use all the exported symbols of of my-package, then use the package:
(defpackage #:my-test-package
(:use
#:common-lisp
#:my-test-runner
#:my-package))
If you want all symbols whose home package is my-package, and you want to import them (so they are directly present in my-test-package rather than just accessible from it) then you want something like this:
(eval-when (:load-toplevel :compile-toplevel :execute)
(defun package-symbols (p)
"All the symbols accessible in P whose home package is P"
(let ((ints '()))
(do-symbols (s p ints)
(when (eq (symbol-package s) p)
(push s ints)))))
(defmacro define-test-package (package &body importing)
`(defpackage ,package
(:use
#:common-lisp #:my-test-runner)
,#(mapcar (lambda (i)
`(:import-from
,i
,#(package-symbols (find-package i))))
importing)))
Then (define-test-package #:my-test-package #:my-package) will do what you want.
I have added the following method to the generic function speak but would now like to remove this particular method in the REPL without removing the rest of the generic functions' methods.
(defmethod speak :around ((c courtier) string) ; [1]
(format t "Does the King believe that ~A?" string)
(if (eql (read) 'yes)
(if (next-method-p) (call-next-method)) ; [2]
(format t "Indeed, it is a preposterous idea.~%"))
'bow)
[1] The :around method replaces the primary method for the type.
[2] Then it decides whether to call the primary method or not.
The documentation link to the function remove-method has no examples and I don't know what is the syntax to refer to the actual :around method above.
(remove-method #'speak)
TOO FEW ARGUMENTS
(remove-method #'speak :around)
NO-APPLICABLE-METHOD
From the documentation:
remove-method generic-function method
It expects a generic function object and a method object as arguments.
One can find the method via find-method.
CL-USER 39 > (find-method #'speak
(list :around)
(list (find-class 'courtier) (find-class t)))
#<STANDARD-METHOD SPEAK (:AROUND) (COURTIER T) 42001285EB>
CL-USER 40 > (remove-method #'speak
(find-method #'speak
(list :around)
(list (find-class 'courtier)
(find-class t))))
#<STANDARD-GENERIC-FUNCTION SPEAK 422000A68C>
Note also that a good Lisp development environment may also allow to remove methods in the editor or the inspector.
Note that in the Lisp listener, one does not need to call find-method twice like above. The variable * contains the last result.
CL-USER 43 > (find-method #'speak
(list :around)
(list (find-class 'courtier)
(find-class t)))
#<STANDARD-METHOD SPEAK (:AROUND) (COURTIER T) 4200150DEB>
CL-USER 44 > (remove-method #'speak *)
#<STANDARD-GENERIC-FUNCTION SPEAK 422000A68C>
Here is another interaction example using SLIME in GNU Emacs with the presentation feature for SLIME enabled. A presentation is Lisp output, which keeps the connection between the printed object and the generated text.
Call the find-method function. It returns the method. Here we use presentations, which keep the connections between text and Lisp objects. The output is displayed in the color red and it is mouse-sensitive. Moving the mouse over the red returned object will add interaction options.
Now type (remove-method #'speak and then middle-click (or whatever SLIME is configured to use) on the red output: the presentation (the text and the connected object) will be copied to the line. Type ) and enter the form. SLIME has actually constructed a list with the real object and not the textual representation, then.
This is how repls work on the Symbolics Lisp Machine and in CLIM / McCLIM...
If using GNU Emacs with SLIME, you can also use slime-inspector. For example define generic function foo and two methods:
USER> (defgeneric foo (x))
#<STANDARD-GENERIC-FUNCTION FOO (0)>
USER> (defmethod foo ((x string)) (length x))
#<STANDARD-METHOD FOO (STRING) {100B4D7E23}>
USER> (defmethod foo ((x integer)) x)
#<STANDARD-METHOD FOO (INTEGER) {100C355843}>
You have two main options to enter the inspector:
From the REPL, type #'foo so that a presentation object for the generic method is printed:
USER> #'foo
#<STANDARD-GENERIC-FUNCTION FOO (0)>
Either right-click the presentation (anywhere inside #<...>) and select Inspect, or put the cursor in the presentation and press C-c C-v TAB (slime-inspect-presentation-at-point).
From a source file, enter slime-inspect, a.k.a. C-c I, and enter #'foo.
In both cases, you are shown a view similar to this:
#<STANDARD-GENERIC-FUNCTION {505A9A2B}>
--------------------
Name: FOO
Arguments: (X)
Method class: #<STANDARD-CLASS COMMON-LISP:STANDARD-METHOD>
Method combination: #<SB-PCL::STANDARD-METHOD-COMBINATION STANDARD () {1000214003}>
Methods:
(INTEGER) [remove method]
(STRING) [remove method]
(....)
Each [remove method] text is actually a button, click or press Return on any of them to remove the associated method from the generic function.
Say if I define a metaclass that enhances standard slots with a validator slot, when I pass :validator (clavier:valid-email "The email is invalid") as an option, instead of storing the result of of the expression, which is a funcallable, it stores the expression itself. Am I'm missing a step when extending the standard slots? How do I ensure the expression is evaluated before stored? I'm using SBCL 1.2.11 btw. Here is the code in question
(unless (find-package 'clavier)
(ql:quickload :clavier))
(unless (find-package 'c2mop)
(ql:quickload :c2mop))
(defpackage #:clos2web/validation
(:use #:cl)
(:import-from #:c2mop
#:standard-class
#:standard-direct-slot-definition
#:standard-effective-slot-definition
#:validate-superclass
#:direct-slot-definition-class
#:effective-slot-definition-class
#:compute-effective-slot-definition
#:slot-value-using-class))
(in-package #:clos2web/validation)
(defun true (value)
"Always return true."
(declare (ignore value))
t)
(defclass validation-class (standard-class)
()
(:documentation "Meta-class for objects whose slots know how to validate
their values."))
(defmethod validate-superclass
((class validation-class) (super standard-class))
t)
(defmethod validate-superclass
((class standard-class) (super validation-class))
t)
(defclass validation-slot (c2mop:standard-slot-definition)
((validator :initarg :validator :accessor validator :initform #'true
:documentation "The function to determine if the value is
valid. It takes as a parameter the value.")))
(defclass validation-direct-slot (validation-slot
standard-direct-slot-definition)
())
(defclass validation-effective-slot (validation-slot
standard-effective-slot-definition)
())
(defmethod direct-slot-definition-class ((class validation-class) &rest initargs)
(declare (ignore initargs))
(find-class 'validation-direct-slot))
(defmethod effective-slot-definition-class ((class validation-class) &rest initargs)
(declare (ignore initargs))
(find-class 'validation-effective-slot))
(defmethod compute-effective-slot-definition
((class validation-class) slot-name direct-slot-definitions)
(let ((effective-slot-definition (call-next-method)))
(setf (validator effective-slot-definition)
(some #'validator direct-slot-definitions))
effective-slot-definition))
(defmethod (setf slot-value-using-class) :before
(new (class validation-class) object (slot validation-effective-slot))
(when (slot-boundp slot 'validator)
(multiple-value-bind (validp msg)
(funcall (validator slot) new)
(unless validp
(error msg)))))
;; Example usage
(defclass user ()
((name :initarg :name)
(email :initarg :email :validator (clavier:valid-email "The email is invalid") :accessor email))
(:metaclass validation-class))
(let ((pepe (make-instance 'user :name "Pepe" :email "pepe#tumadre.com")))
(setf (email pepe) "FU!")) ;; should throw
The code fails when making an instance as (CLAVIER:VALID-EMAIL "The email is invalid") is not a funcallable.
(CLAVIER:VALID-EMAIL
"The email is invalid") fell through ETYPECASE expression.
Wanted one of (FUNCTION SYMBOL).
[Condition of type SB-KERNEL:CASE-FAILURE]
Like the comment above says, defclass does not evaluate arguments (it is a macro). While the usual advice is to avoid eval, I think that eval in this circumstance might be exactly what you want. While usually you would splice the form directly into some macro body, with defclass I think the answer is to eval the form in slot initialization and store the evaluation (if it has not yet been evaled).
This would probably occur in:
(defmethod initialize-instance :after ((obj validation-slot)
&key &allow-other-keys)
#| ... |#)
Optionally you could also store the :validation-message and :validation-fn as two separate arguments then call:
(multiple-value-bind (validp msg)
(funcall (funcall (validator-fn slot)
(validator-message slot))
new)
(unless validp
(error msg)))
Another alternative would be to store the evaluation of the form and pass that to the macro:
(defvar *email-validator* (CLAVIER:VALID-EMAIL "The email is invalid"))
(defun email-validator (val)
(funcall *email-validator* val))
Then pass email-validator to defclass.
Additionally I might suggest that your validation functions signal slot-validation-error type conditions instead of error type conditions. Then your condition could contain references to the validator that failed, the value, the slot and the instance. This could give you much better control than the raw error. You could also add some restarts (abort to skip setting the slot, use-value to provide a different value).
Depending on your setup, it might also make more sense for your validation function to signal these directly instead of returning multiple values that are then coerced to signals.
Is there a way to access slots of superclasses in CLOS?
E.g., in Objective C I can perform
- (void) frob {
[super frob]
}
This sends a message to the (sole) superclass of frob.
Perusing the CLOS documentation suggests that DEFCLASS merges all superclass information on class creation and thus this ability to communicate with the superclass is lost. Is this correct?
edit:
The scenario is somewhat unusual:
Given classes
(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))
How to have a method, say, BEHAVIOR-TYPES or GET-BEHAVIOR that, when called with an object of type horse, returns '(:eat :sleep :drink :buck :gambol :neigh). That is to say, inheritance via a slot "adds" to the initform rather than replaces it.
An easy solution is, rather than to assign the data to the class, to have a generic method like so:
(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)))
However, this solution moves the data into the defgeneric rather than the class, where it properly belongs. So the motivation for the question came forth out of this.
At any rate - the question as asked reflected a misunderstanding of CLOS's design. It is not possible, as asked and within the normal framework, to perform this task. However, two separate approaches are given below using the MOP to solve the problem I posed.
The title of your question makes it sound like you're asking about how to access slots, but the code you show seems more like it's about calling methods that have been specialized for the superclass. If it's the latter that you're looking for, you should take a look at call-next-method, as well as 7.6 Generic Functions and Methods from the HyperSpec.
Calling “superclass methods”
In CLOS, methods don't belong to classes like they do in some other languages. Instead, there are generic functions on which specialized methods are defined. For a given argument list, a number of methods may be applicable, but only one is most specific. You can call the next most specific method with call-next-method. In the following transcript, there's a class FOO and a subclass BAR, and a generic function FROB which has methods specialized for FOO and BAR. In the method specialized for BAR, there's a call to call-next-method which, in this case, calls the method specialized for 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
Simulating it with method combinations
You can use method combinations to combine the results of the methods that are applicable to a list of arguments. For instance, you can define a method a with the method combination list that means when you call (a thing), all the methods on a applicable for the argument are called, and their results are combined into a list. If you give your slots in the different classes different names, and specialize methods on a that read those values, you can simulate the sort of thing you're looking for. This doens't prevent you from also using a traditional reader that accesses the slot, as well (e.g., get-a in the following example). The following code shows an example:
(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
Using the MOP
This post from 1998 on Allegro CL archives is worth a read. It sounds like the author is looking for something similar to what you're looking for.
I need to define an inheritance behavior that concatenates
string-values of superclass-initforms with local slot initforms. E.g.
(defclass super()
((f :accessor f :initform "head")) (:metaclass user-class))
(defclass sub(super)
((f :accessor f :initform "tail")) (:metaclass user-class))
I'd like to get the following:
(f(make-instance'sub)) -> "head tail"
I didn't find a standard option in defclass slot-descriptions for
this. I'd like to define the concatenate combination for each
meta-class 'user-class'.
The response (by Heiko Kirschke, not me, but also see this response from Jon White with a similar approach), defines a new type of class:
(defclass user-class (standard-class) ())
and specializes clos:compute-effective-slot-definition to provide an initform that's computed from the slot definitions of the class and its superclass(es):
(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))
Then it's used like this:
(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"
This is getting into MOP functionality that's not specified by the spec, so you might have to adapt it for your particular implementation. There are some MOP compatibility layer packages out there that might be able to help you out, though.
There is no such concept as the instance slot of a superclass in CLOS.
If you create an instance, it has all slots. All slots from the class and its superclasses.
If a class has a slot FOO and some superclasses have also slots named FOO, all those are merged into one slot. Each instance of that CLOS class will have that slot.
Still you need to be more careful with your wording. Superclasses are objects themselves and they have slots themselves. But this has nothing to do with an instance having local slots and having superclasses with instance slots. The latter does not exist in CLOS.
CL-USER 18 > (defclass bar () (a b))
#<STANDARD-CLASS BAR 413039BD0B>
Above is then a superclass with two slots.
CL-USER 19 > (defclass foo (bar) (b c))
#<STANDARD-CLASS FOO 4130387C93>
Above is a class with two local and one inherited slot. The slot b is actually merged from this class and from the superclass.
CL-USER 20 > (describe (make-instance 'foo))
#<FOO 402000951B> is a FOO
B #<unbound slot>
C #<unbound slot>
A #<unbound slot>
Above shows that the instance has three slots and all can be directly accessed. Even the slot `a, which was defined in the superclass.
If we look at the actual superclass as an instance itself, we see its slots:
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
This is really, really hasckish. I hope someone will step in and fix it, though it should illustrate the idea:
(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. I see that the function that computes the list of slots definitions in SBCL is in std-class.lisp, std-compute-slots. So it isn't something that MOP defines in some way... But this one would be really helpful here.
In SBCL, when I define new metaclass
CL-USER> (defclass counting-class (standard-class)
((counter :initform 0)))
#<STANDARD-CLASS COUNTING-CLASS>
and add a method to the GF "make-instance":
CL-USER> (defmethod make-instance :after ((class counting-class) &key)
(incf (slot-value class 'counter)))
#<STANDARD-METHOD MAKE-INSTANCE :AFTER (COUNTING-CLASS) {25302219}>
I receive an error, if I try to create an Instance:
CL-USER> (defclass counted-point () (x y) (:metaclass counting-class))
The class #<STANDARD-CLASS STANDARD-OBJECT> was specified as a
super-class of the class #<COUNTING-CLASS COUNTED-POINT>, but
the meta-classes #<STANDARD-CLASS STANDARD-CLASS> and
#<STANDARD-CLASS COUNTING-CLASS> are incompatible. Define a
method for SB-MOP:VALIDATE-SUPERCLASS to avoid this error.
Now, if I add the required Definition:
CL-USER> (defmethod sb-mop:validate-superclass ((class counting-class)
(super standard-class))
t)
#<STANDARD-METHOD SB-MOP:VALIDATE-SUPERCLASS (COUNTING-CLASS STANDARD-CLASS) {26443EC9}>
It works:
CL-USER> (defclass counted-point () (x y) (:metaclass counting-class))
#<COUNTING-CLASS COUNTED-POINT>
My Question is: Why is this required?
From my POV it should be sufficient, to declare counting-class as an derivative of standard-class, as I did in the first step.
CLOS MOP spec for validate-superclass says that the default method returns t only in trivial cases and adds:
Defining a method on validate-superclass requires detailed knowledge
of of the internal protocol followed by each of the two class
metaobject classes. A method on validate-superclass which returns true
for two different class metaobject classes declares that they are
compatible.
You could consider your validate-superclass to be a declaration that you understand what you are doing.
Incidentally, I think you can define a class which would count its instances easier.
PS. Some implementations also return t in some other cases.