Discussion:
macros expanding to defclass + some mop code
Sam Steingold
2010-12-27 03:37:17 UTC
Permalink
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?
--
Sam Steingold (http://sds.podval.org/) on Ubuntu 10.04 (lucid)
http://www.memritv.org http://www.PetitionOnline.com/tap12009/
http://ffii.org http://pmw.org.il http://palestinefacts.org
Perl: all stupidities of UNIX in one.
Pascal Costanza
2010-12-27 07:02:25 UTC
Permalink
Post by Sam Steingold
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
`(,(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)))
(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
--
Pascal Costanza, mailto:pc-***@public.gmane.org, http://p-cos.net
Vrije Universiteit Brussel
Software Languages Lab
Pleinlaan 2, B-1050 Brussel, Belgium
Sam Steingold
2010-12-27 14:28:48 UTC
Permalink
Post by Pascal Costanza
Post by Sam Steingold
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
                    `(,(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)))
(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.
Post by Pascal Costanza
If you are really sure that the class doesn't change at runtime,
I am.
Post by Pascal Costanza
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.
Post by Pascal Costanza
You could of course also decide to parse the slot definition forms yourself.
I am too lazy for that :-(
--
Sam Steingold <http://sds.podval.org>
Alessio Stalla
2010-12-27 14:43:54 UTC
Permalink
Post by Sam Steingold
Post by Pascal Costanza
Post by Sam Steingold
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
                    `(,(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)))
(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.
Post by Pascal Costanza
If you are really sure that the class doesn't change at runtime,
I am.
Post by Pascal Costanza
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.
Post by Pascal Costanza
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
Martin Simmons
2010-12-27 16:22:38 UTC
Permalink
Post by Sam Steingold
Post by Pascal Costanza
Post by Sam Steingold
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
                    `(,(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)))
(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.
Post by Pascal Costanza
If you are really sure that the class doesn't change at runtime,
I am.
Post by Pascal Costanza
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))))
Post by Sam Steingold
I wonder if the deprecated #, will help me.
Using load-time-value would be better.
--
Martin Simmons
LispWorks Ltd
http://www.lispworks.com/
Pascal Costanza
2010-12-29 20:37:44 UTC
Permalink
Post by Sam Steingold
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
`(,(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)))
(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, mailto:pc-***@public.gmane.org, http://p-cos.net
Vrije Universiteit Brussel
Software Languages Lab
Pleinlaan 2, B-1050 Brussel, Belgium
Sam Steingold
2010-12-30 00:05:43 UTC
Permalink
Post by Pascal Costanza
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.
--
Sam Steingold (http://sds.podval.org/) on Ubuntu 10.04 (lucid)
http://www.PetitionOnline.com/tap12009/ http://dhimmi.com http://memri.org
http://camera.org http://ffii.org http://iris.org.il http://honestreporting.com
In C you can make mistakes, while in C++ you can also inherit them!
Pascal Costanza
2010-12-30 19:12:43 UTC
Permalink
Post by Sam Steingold
Post by Pascal Costanza
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
--
Pascal Costanza, mailto:pc-***@public.gmane.org, http://p-cos.net
Vrije Universiteit Brussel
Software Languages Lab
Pleinlaan 2, B-1050 Brussel, Belgium
Daniel Weinreb
2010-12-31 18:32:15 UTC
Permalink
Post by Pascal Costanza
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

Loading...