Hi, I want to write a macro which would expand to a defclass + some code which uses the resulting class object using mop. e.g. (untested), (defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x) (list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))))
which should expand (deffoo bar ((a :reader bar-a) (b :reader bar-b))) to something like this: (progn (defclass bar () ((a :reader bar-a) (b :reader bar-b))) (defun foo (x) (list (bar-a x) (bar-b x))))
Alas, CLASS is not defined at read time when (class-direct-slots (find-class class)) and (slot-definition-readers ds) want to be evaluated.
So, how do I do this?
On 27 Dec 2010, at 04:37, Sam Steingold wrote:
Hi, I want to write a macro which would expand to a defclass + some code which uses the resulting class object using mop. e.g. (untested), (defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x) (list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))))
which should expand (deffoo bar ((a :reader bar-a) (b :reader bar-b))) to something like this: (progn (defclass bar () ((a :reader bar-a) (b :reader bar-b))) (defun foo (x) (list (bar-a x) (bar-b x))))
Alas, CLASS is not defined at read time when (class-direct-slots (find-class class)) and (slot-definition-readers ds) want to be evaluated.
So, how do I do this?
Since a class may be redefined at runtime, you want to use the MOP at runtime anyway. So the code should rather expand into something like this:
(defun foo (x) (loop for slot in (class-direct-slots (find-class 'some-class)) collect (slot-value-using-class (find-class 'some-class) x slot)))
There are ways to tweak this, but the principle should be clear.
If you are really sure that the class doesn't change at runtime, you can alternatively wrap the defclass form into an (eval-when (:compile-toplevel :load-toplevel :execute) ...)
You could of course also decide to parse the slot definition forms yourself.
Pascal
On Mon, Dec 27, 2010 at 2:02 AM, Pascal Costanza pc@p-cos.net wrote:
On 27 Dec 2010, at 04:37, Sam Steingold wrote:
I want to write a macro which would expand to a defclass + some code which uses the resulting class object using mop. e.g. (untested), (defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x) (list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))))
which should expand (deffoo bar ((a :reader bar-a) (b :reader bar-b))) to something like this: (progn (defclass bar () ((a :reader bar-a) (b :reader bar-b))) (defun foo (x) (list (bar-a x) (bar-b x))))
Alas, CLASS is not defined at read time when (class-direct-slots (find-class class)) and (slot-definition-readers ds) want to be evaluated.
So, how do I do this?
Since a class may be redefined at runtime, you want to use the MOP at runtime anyway.
no, these classes will not change at run time (they are actually structs); and even if they will, I will be using deffoo for that.
If you are really sure that the class doesn't change at runtime,
I am.
you can alternatively wrap the defclass form into an (eval-when (:compile-toplevel :load-toplevel :execute) ...)
nope. my macro calls MOP functions at macroexpansion time, so this eval-when will not help me. I wonder if the deprecated #, will help me.
You could of course also decide to parse the slot definition forms yourself.
I am too lazy for that :-(
On Mon, Dec 27, 2010 at 3:28 PM, Sam Steingold sds@gnu.org wrote:
On Mon, Dec 27, 2010 at 2:02 AM, Pascal Costanza pc@p-cos.net wrote:
On 27 Dec 2010, at 04:37, Sam Steingold wrote:
I want to write a macro which would expand to a defclass + some code which uses the resulting class object using mop. e.g. (untested), (defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x) (list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))))
which should expand (deffoo bar ((a :reader bar-a) (b :reader bar-b))) to something like this: (progn (defclass bar () ((a :reader bar-a) (b :reader bar-b))) (defun foo (x) (list (bar-a x) (bar-b x))))
Alas, CLASS is not defined at read time when (class-direct-slots (find-class class)) and (slot-definition-readers ds) want to be evaluated.
So, how do I do this?
Since a class may be redefined at runtime, you want to use the MOP at runtime anyway.
no, these classes will not change at run time (they are actually structs); and even if they will, I will be using deffoo for that.
If you are really sure that the class doesn't change at runtime,
I am.
you can alternatively wrap the defclass form into an (eval-when (:compile-toplevel :load-toplevel :execute) ...)
nope. my macro calls MOP functions at macroexpansion time, so this eval-when will not help me. I wonder if the deprecated #, will help me.
You could of course also decide to parse the slot definition forms yourself.
I am too lazy for that :-(
How about:
(defmacro deffoo (class slots) `(progn ,(let ((defclass-form `(defclass ,class () ,slots))) (eval defclass-form) defclass-form) (defun foo (x) ...)))
This will execute the defclass-form twice, if you compile and load the code containing the macro in the same session. So with structs it might not work if your implementation signals an error on struct redefinition (which IIRC it can do).
hth, Alessio
On Mon, 27 Dec 2010 09:28:48 -0500, Sam Steingold said:
On Mon, Dec 27, 2010 at 2:02 AM, Pascal Costanza pc@p-cos.net wrote:
On 27 Dec 2010, at 04:37, Sam Steingold wrote:
I want to write a macro which would expand to a defclass + some code which uses the resulting class object using mop. e.g. (untested), (defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x) (list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))))
which should expand (deffoo bar ((a :reader bar-a) (b :reader bar-b))) to something like this: (progn (defclass bar () ((a :reader bar-a) (b :reader bar-b))) (defun foo (x) (list (bar-a x) (bar-b x))))
Alas, CLASS is not defined at read time when (class-direct-slots (find-class class)) and (slot-definition-readers ds) want to be evaluated.
So, how do I do this?
Since a class may be redefined at runtime, you want to use the MOP at runtime anyway.
no, these classes will not change at run time (they are actually structs); and even if they will, I will be using deffoo for that.
If you are really sure that the class doesn't change at runtime,
I am.
you can alternatively wrap the defclass form into an (eval-when (:compile-toplevel :load-toplevel :execute) ...)
nope. my macro calls MOP functions at macroexpansion time, so this eval-when will not help me.
This might work (by delaying the MOP functions until the defclass has been evaluated at compile-time).
(defmacro list-all-slot-values-of-class-name (class) `(list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))
(defmacro deffoo (class slots) `(progn (eval-when (:compile-toplevel :load-toplevel :execute) (defclass ,class () ,slots)) (defun foo (x) (list-all-slot-values-of-class-name ,class))))
I wonder if the deprecated #, will help me.
Using load-time-value would be better.
On 27 Dec 2010, at 04:37, Sam Steingold wrote:
Hi, I want to write a macro which would expand to a defclass + some code which uses the resulting class object using mop. e.g. (untested), (defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x) (list ,@(mapcar (lambda (ds) `(,(car (slot-definition-readers ds)) x)) (class-direct-slots (find-class class)))))))
which should expand (deffoo bar ((a :reader bar-a) (b :reader bar-b))) to something like this: (progn (defclass bar () ((a :reader bar-a) (b :reader bar-b))) (defun foo (x) (list (bar-a x) (bar-b x))))
Alas, CLASS is not defined at read time when (class-direct-slots (find-class class)) and (slot-definition-readers ds) want to be evaluated.
So, how do I do this?
Just to be complete, I insist that the 'right' way to do this is to do this at runtime. Here is a sketch of how to do this in such a way that you don't suffer that much from additional runtime overheads:
(defclass foo-dependent () ((function-symbol :initarg :function-symbol))
(defmethod update-dependent ((class standard-class) (dependent foo-dependent) &rest initargs) (declare (ignore initargs)) (setf (symbol-function (slot-value dependent 'function-symbol)) (compile nil `(lambda (x) (list ,@(loop for slot in (class-direct-slots class) for name = (slot-definition-name slot) collect `(slot-value x ',name)))))))
(defmacro deffoo (class slots) `(progn (defclass ,class () ,slots) (defun foo (x)) (add-dependent (find-class ',class) (make-instance 'foo-dependent :function-symbol 'foo)) (reinitialize-instance (find-class ',class)) ',class))
[Untested.]
Other variations are possible, like using a method on finalize-inheritance instead of update-dependent, if you can afford to have a separate metaclass for this. If you are sure that the class doesn't change at runtime, you can still generate the body of the foo function at loadtime / runtime, after the complete class exists.
Of course, this adds to some amount of load-time overhead, which may not be acceptable under certain circumstances.
Pascal
- Pascal Costanza cp-99BKOWH6pVcrbJU0hmoH5j@choyvp.tznar.bet [2010-12-29 21:37:44 +0100]:
Just to be complete, I insist that the 'right' way to do this is to do this at runtime.
I found Martin's solution - moving the MOP code into an outside macro, thus moving the MOP function calls from read time to macroexpansion time - to be the most elegant and simple.
thanks.
On 30 Dec 2010, at 01:05, Sam Steingold wrote:
- Pascal Costanza cp-99BKOWH6pVcrbJU0hmoH5j@choyvp.tznar.bet [2010-12-29 21:37:44 +0100]:
Just to be complete, I insist that the 'right' way to do this is to do this at runtime.
I found Martin's solution - moving the MOP code into an outside macro, thus moving the MOP function calls from read time to macroexpansion time
- to be the most elegant and simple.
I wasn't trying to criticize Martin's solution - it is probably the best solution for many situations. That's why I put the word 'right' in quotation marks.
I only wanted to call attention to the fact that doing such things at runtime is more in line with how the CLOS MOP (and CLOS for that matter) is designed, which is as a runtime metaobject protocol. For example, there can be circumstances where the two classes that Martin's solution creates may not match and may be different at compile time and runtime. The solution I proposed last avoids that by having exactly one class definition. However, such cases are unlikely to occur in practice, so this is likely just an academic exercise. Nevertheless, I think it's important to mention this just for the sake of completeness.
I hope this is clearer now.
Best, Pascal
On 12/30/10 2:12 PM, Pascal Costanza wrote:
The solution I proposed last avoids that by having exactly one class definition. However, such cases are unlikely to occur in practice, so this is likely just an academic exercise. Nevertheless, I think it's important to mention this
I agree that mentioning this is a good idea. Part of the whole idea of the "pro" mailing list is to be a place where we can all share knowledge about the more complex/subtle issues in Common Lisp. Thanks for clearing this up!
-- Dan