Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Obtaining the type of a macro argument

0 views
Skip to first unread message

TJ Atkins

unread,
Mar 26, 2009, 11:58:33 AM3/26/09
to
Hey all, I'm putting together a function that will nondestructively
modify a structure. Since structures don't support indirection on
slot access, I've done it as a macro so I can build the appropriate
accessors as needed. Here's (unworking) code.

(defmacro w/slots (struct &rest slots-vals)
"Nondestructively modifies the specified slots in the passed
struct.
(w/slots *struct* :foo bar :baz quux) will return a copy of
*struct* with the foo
and baz slots changed appropriately."
(let ((x (gensym))
(struct-name (symbol-name (type-of struct))))
`(let ((,x (copy-structure ,struct)))
,@(loop for (slotname slotval) in
(group slots-vals 2 :pad nil)
for accessor =
(intern (concatenate 'string
struct-name
"-"
(symbol-name slotname)))
collect `(setf (,accessor ,x) ,slotval))
,x)))

The reason this doesn't work is the (type-of struct) near the
beginning, which returns "symbol" no matter what.

The strange thing is that I previously had (type-of (eval struct)),
which worked! Then I saved this over to my utils file and restarted
my lisp image, and it started trying to eval the name of the variable
as a function.

So, any ideas? Also, if there's a better way to do this, let me know.

Wade

unread,
Mar 26, 2009, 12:53:22 PM3/26/09
to
On Mar 26, 9:58 am, TJ Atkins <JackalM...@gmail.com> wrote:

> So, any ideas?  Also, if there's a better way to do this, let me know.

You cannot really depend on a struct having accessors whose
symbols are <struct-name>-<slot-name>.

A quick and dirty way is just to specify accessor symbols instead of
slot symbols in w/slots.

(defmacro w/slots (struct &rest accessor-values)
(let ((x (gensym)))
`(let ((,x (copy-structure ,struct)))
,@(loop for (accessor slotval) in accessor-values


collect `(setf (,accessor ,x) ,slotval))
,x)))

CL-USER> (defstruct a x y)
A
CL-USER> (defvar ainst (make-a :x 1.0 :y 2.0))
AINST
CL-USER> (w/slots ainst (a-x 3.1) (a-y 10.6))
#S(A :X 3.1 :Y 10.6)
CL-USER> (w/slots ainst (a-x 'wade))
#S(A :X WADE :Y 2.0)
CL-USER>

W


gugamilare

unread,
Mar 26, 2009, 12:57:03 PM3/26/09
to

The problem here is that the macro does not evaluate its arguments.
so, if you call

(w/slots (foo ...) ...), then, inside the macro, the "struct" var will
be bound to the symbol "foo". On the other hand, if you call

(w/slots ((gethash *table* key) ...)), struct will be bound to the
list (gethash *table* key).

There are several ways you can solve this problem. First, instead of
keywords, you give the name of the slots instead. For instance, if you
have:

(defstruct point x y)

then you call

(let ((pt (make-point :x 10 :y 20)))
(w/slots pt x 20))

Note that I passed x, not :x, so the macro would be able to expand it
into something equivalent to

(let ((#:g1 (copy-structure pt)))
(setf (slot-value #:g1 'x) 20))

This doesn't even need to be a macro, it can be a function, but, in
this case, you have to quote the slot names:

(w/slots pt 'x 20)

TJ Atkins

unread,
Mar 26, 2009, 1:27:02 PM3/26/09
to
On Mar 26, 11:53 am, Wade <wade.humen...@gmail.com> wrote:
> On Mar 26, 9:58 am, TJ Atkins <JackalM...@gmail.com> wrote:
>
> > So, any ideas? Also, if there's a better way to do this, let me know.
>
> You cannot really depend on a struct having accessors whose
> symbols are <struct-name>-<slot-name>.

Indeed, but they *usually* are. I can at least depend on it in my own
code.

> A quick and dirty way is just to specify accessor symbols instead of
> slot symbols in w/slots.
>
> (defmacro w/slots (struct &rest accessor-values)
> (let ((x (gensym)))

> `(let ((,x (copy-structure ,struct)))
> ,@(loop for (accessor slotval) in accessor-values


> collect `(setf (,accessor ,x) ,slotval))
> ,x)))
>

> CL-USER> (defstruct a x y)
> A
> CL-USER> (defvar ainst (make-a :x 1.0 :y 2.0))
> AINST
> CL-USER> (w/slots ainst (a-x 3.1) (a-y 10.6))
> #S(A :X 3.1 :Y 10.6)
> CL-USER> (w/slots ainst (a-x 'wade))
> #S(A :X WADE :Y 2.0)
> CL-USER>

Yeah, I was just hoping to keep a nice symmetry with the default
constructor. I may end up having to do that.

~TJ

On Mar 26, 11:57 am, gugamilare <gugamil...@gmail.com> wrote:
> The problem here is that the macro does not evaluate its arguments.
> so, if you call
>
> (w/slots (foo ...) ...), then, inside the macro, the "struct" var will
> be bound to the symbol "foo". On the other hand, if you call
>
> (w/slots ((gethash *table* key) ...)), struct will be bound to the
> list (gethash *table* key).

Indeed, which is my problem. I figured out the issue pretty quickly
when it kept trying to call the function "symbol-foo". ^_^

> There are several ways you can solve this problem. First, instead of
> keywords, you give the name of the slots instead. For instance, if you
> have:
>
> (defstruct point x y)
>
> then you call
>
> (let ((pt (make-point :x 10 :y 20)))
>   (w/slots pt x 20))
>
> Note that I passed x, not :x, so the macro would be able to expand it
> into something equivalent to
>
> (let ((#:g1 (copy-structure pt)))
>   (setf (slot-value #:g1 'x) 20))
>
> This doesn't even need to be a macro, it can be a function, but, in
> this case, you have to quote the slot names:
>
> (w/slots pt 'x 20)

That doesn't work, at least in my Lisp's (Corman Common Lisp)
implementation of CLOS. (slot-value) doesn't work on structs.
Believe me, that was the first thing I tried. It was only when that
failed that I turned to manually creating accessor function symbols.

I may just end up rolling a (defclass*) macro that lets me be as terse
as defstruct, so I can use (slot-value). I just don't like pulling
down the full syntax of defclass when I just need a simple container.

~TJ

TomSW

unread,
Mar 26, 2009, 2:05:30 PM3/26/09
to
On Mar 26, 4:58 pm, TJ Atkins <JackalM...@gmail.com> wrote:
> Hey all, I'm putting together a function that will nondestructively
> modify a structure.  Since structures don't support indirection on
> slot access, I've done it as a macro so I can build the appropriate
> accessors as needed.

Maybe you could rely on with-slots, and the fact that copy-structure
does copy the slots values even though the hyperspec says it doesn't:

(defmacro w/slots (struct-instance &rest slots-values)
(let ((new-struct (gensym)))
`(let ((,new-struct (copy-structure ,struct-instance)))
(with-slots ,(loop for list on slots-values by #'cddr
collect (car list))
,new-struct
(setf ,@slots-values)
,new-struct))))

-- and use the slot names instead of keywords, as in


(w/slots *struct* foo bar baz quux)

Most of the interesting innards of structs seems to be hidden /
implementation specific. You could maybe create an extended version of
defstruct that would gather some of that info for you.

Tom SW

Wade

unread,
Mar 26, 2009, 2:45:51 PM3/26/09
to


How about....

(defmacro defstruct/w/slots (name-and-options &rest args)
(let* ((slots (if (stringp (car args)) (cdr args) args))
(w/slot-keys (mapcar (lambda (slot)
`(,slot nil ,(intern (format nil "~A-~A" slot 'specified-p))))
slots))
(struct-name (if (consp name-and-options) (car name-and-options)
name-and-options))
(conc-name (intern (format nil "~A-~A" 'make struct-name))))
`(prog1
(eval-when (:compile-toplevel :load-toplevel :execute)
(defstruct ,name-and-options ,@args))
(defmethod w/slots ((obj ,struct-name) &key ,@w/slot-keys)
(,conc-name
,@(loop for (key value specified-p) in w/slot-keys
append `(,(intern (string key) :keyword)
(if ,specified-p ,key (,(intern (format nil "~A-~A" struct-name
key)) obj)))))))))

CL-USER> (defstruct/w/slots a x y)
A
CL-USER> (defvar a (make-a :x 1.0 :y 2.0))
A
CL-USER> a
#S(A :X 1.0 :Y 2.0)
CL-USER> (w/slots a :x 3.0)
#S(A :X 3.0 :Y 2.0)
CL-USER>
CL-USER> (pprint (macroexpand '(defstruct/w/slots a x y)))

(LET ((#:G14
(EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
(DEFSTRUCT A X Y))))
(DEFMETHOD W/SLOTS ((OBJ A) &KEY (X NIL X-SPECIFIED-P) (Y NIL Y-
SPECIFIED-P))
(MAKE-A :X
(IF X-SPECIFIED-P X (A-X OBJ))
:Y
(IF Y-SPECIFIED-P Y (A-Y OBJ))))
#:G14); No value
CL-USER>

Not sure if it is complete. It would need work to be totally
general.

W

Thomas F. Burdick

unread,
Mar 26, 2009, 3:09:28 PM3/26/09
to

Let's take a slightly simpler form as the starting point:

(change x :slot y)

should do

(setf (frob-slot x) y)

This means you need to find out the class-name of the object that X
evaluates to, at run time, then calculate the name frob-slot from
that, and evaluate a call to the setf form, based on that. So to solve
the problem you describe, you'd be stuck with a function like this:

(defun change (obj slot val)
(let* ((struct-name (class-of obj))
(accessor (intern (concatenate 'string
(string struct-name) "-" (string slot))
(symbol-package struct-name))))
(eval `(setf (,accessor ',obj) ',val))))

Computing the accessor name *must* be put off until runtime, because
you don't necessarily have all the information you need to do it until
then.

But how do you know that you've come up with legit accessor names?
From a reply later in the thread, it seems that you control the
defstruct forms involved. In that case, just use a wrapper around
defstruct, and have that generate a defmethod that does what you need.
So:

(defstruct* foo x y z)

becomes

(progn
(defstruct foo x y z)
(defmethod w/slots ((obj foo) &key (x x? nil) (y y? nil) (z z? nil))
"w/slots is a terrible name for this function -- cf Kenny's rule
about names"
(let ((obj (copy-foo obj)))
(when x? (setf (foo-x obj) x)
(when y? ...)
(when z? ...)
obj)))

There's more runtime dispatch than needed in cases like this:

(let* ((f1 (make-foo :z "zed"))
(f2 (w/slots f1 :z "zee")))
...)

But macros must be able to do everything they need to do statically,
and in this case you need more (runtime) flexibility than that.

TJ Atkins

unread,
Mar 26, 2009, 4:40:06 PM3/26/09
to
On Mar 26, 1:05 pm, TomSW <tom.weissm...@gmail.com> wrote:

> On Mar 26, 4:58 pm, TJ Atkins <JackalM...@gmail.com> wrote:
>
> > Hey all, I'm putting together a function that will nondestructively
> > modify a structure. Since structures don't support indirection on
> > slot access, I've done it as a macro so I can build the appropriate
> > accessors as needed.
>
> Maybe you could rely on with-slots, and the fact that copy-structure
> does copy the slots values even though the hyperspec says it doesn't:
>
> (defmacro w/slots (struct-instance &rest slots-values)
> (let ((new-struct (gensym)))
> `(let ((,new-struct (copy-structure ,struct-instance)))
> (with-slots ,(loop for list on slots-values by #'cddr
> collect (car list))
> ,new-struct
> (setf ,@slots-values)
> ,new-struct))))
>
> -- and use the slot names instead of keywords, as in
> (w/slots *struct* foo bar baz quux)
>
> Most of the interesting innards of structs seems to be hidden /
> implementation specific. You could maybe create an extended version of
> defstruct that would gather some of that info for you.
>
> Tom SW

I'd love to do something so simple, but that doesn't work either.
With-slots expands (setf slot-name val)s into something equivalent to
(setf (slot-value instance slot-name) val), and my Lisp already
doesn't allow that.

On Mar 26, 2:09 pm, "Thomas F. Burdick" <tburd...@gmail.com> wrote:
> (progn
>   (defstruct foo x y z)
>   (defmethod w/slots ((obj foo) &key (x x? nil) (y y? nil) (z z? nil))
>     "w/slots is a terrible name for this function -- cf Kenny's rule
> about names"
>     (let ((obj (copy-foo obj)))
>       (when x? (setf (foo-x obj) x)
>       (when y? ...)
>       (when z? ...)
>       obj)))
>
> There's more runtime dispatch than needed in cases like this:
>
>   (let* ((f1 (make-foo :z "zed"))
>          (f2 (w/slots f1 :z "zee")))
>     ...)
>
> But macros must be able to do everything they need to do statically,
> and in this case you need more (runtime) flexibility than that.

This sounds like a winner (hat tip to Wade for a similar response).

Now, naming. I'm not particularly happy with w/slots either, but with-
slots was already taken, and the with- prefix generally refers to a
different idiom anyway. I took a cue from f-set, which uses 'with' as
its analog to 'cons' for its data structures. Maybe I'll just steal
'with' directly? Sounds good to me. Now, to hacking!

~TJ

TJ Atkins

unread,
Mar 26, 2009, 5:05:48 PM3/26/09
to

And yup, that worked just fine. Thanks for the assistance, all.

~TJ

0 new messages