From: Ulf Wikstr?m
Subject: Getting slot names from struct instances
Date: 
Message-ID: <4a10db93.0503102230.30e79c28@posting.google.com>
Hello!
I'd like to be able to do something like:

(defstruct vector x y)

(defun length (v)
  (with-slot-names v
    (sqrt (* x x) (* y y))))

But how do I get the slot names from v (if possible)?

From: Kaz Kylheku
Subject: Re: Getting slot names from struct instances
Date: 
Message-ID: <1110528291.786739.179080@z14g2000cwz.googlegroups.com>
Ulf Wikstr?m wrote:
> Hello!
> I'd like to be able to do something like:
>
> (defstruct vector x y)

Oops, vector is already a type name. :)

> (defun length (v)
>   (with-slot-names v
>     (sqrt (* x x) (* y y))))

There is no portable way to reflect over slot names, but you could use
a customized DEFSTRUCT-like macro to stash the symbols somewhere.

Regardless of whether we can do it or not, this is a bad design idea.
Now the lexical scope can be manipulated from a distance. Add a new
slot to the structure definition, and suddenly every single
WITH-SLOT-NAMES instance throughout the code base acquires a lexically
apparent binding, which may shadow an existing binding and change the
meaning of the code.

Also, this gives you no way to assign renamed bindings to more than one
instance of the structure, as in:

  (defun distance (v0 v1)
    (with-slots ((x0 x) (y0 y)) v0
      (with-slots ((x1 x) (y1 y)) v1
        (sqrt (+ (* (- x1 x0) (- x1 x0))
                 (* (- y1 y0) (- y1 y0)))))))


It's nice to have the assurance that when you see an identifier being
used in Lisp code, you can trace it outward through the lexical scope
to find the closest enclosing lexical definition of it; and that, if
you fail to find one, you can assume it's a free reference: either
unbound, or referring to a special variable. Constructs like
WITH-SLOT-NAMES break that assurance; now you don't know whether or not
any reference enclosed by that construct is captured by it, without
looking up the structure or class definition.
From: Pascal Bourguignon
Subject: Re: Getting slot names from struct instances
Date: 
Message-ID: <873bv2motv.fsf@thalassa.informatimago.com>
"Kaz Kylheku" <···@ashi.footprints.net> writes:

> Ulf Wikstr?m wrote:
> > Hello!
> > I'd like to be able to do something like:
> >
> > (defstruct vector x y)
> 
> Oops, vector is already a type name. :)
> 
> > (defun length (v)
> >   (with-slot-names v
> >     (sqrt (* x x) (* y y))))
> 
> There is no portable way to reflect over slot names, but you could use
> a customized DEFSTRUCT-like macro to stash the symbols somewhere.
> 
> Regardless of whether we can do it or not, this is a bad design idea.
> Now the lexical scope can be manipulated from a distance. Add a new
> slot to the structure definition, and suddenly every single
> WITH-SLOT-NAMES instance throughout the code base acquires a lexically
> apparent binding, which may shadow an existing binding and change the
> meaning of the code.
> 
> Also, this gives you no way to assign renamed bindings to more than one
> instance of the structure, as in:
> 
>   (defun distance (v0 v1)
>     (with-slots ((x0 x) (y0 y)) v0
>       (with-slots ((x1 x) (y1 y)) v1
>         (sqrt (+ (* (- x1 x0) (- x1 x0))
>                  (* (- y1 y0) (- y1 y0)))))))
> 
> 
> It's nice to have the assurance that when you see an identifier being
> used in Lisp code, you can trace it outward through the lexical scope
> to find the closest enclosing lexical definition of it; and that, if
> you fail to find one, you can assume it's a free reference: either
> unbound, or referring to a special variable. Constructs like
> WITH-SLOT-NAMES break that assurance; now you don't know whether or not
> any reference enclosed by that construct is captured by it, without
> looking up the structure or class definition.

This is right, but sometimes you really need the convenience over the
safety.  In my solution, for a structure named REC, I define a macro
named WITH-REC which allows to see the reference to the structure and
therefore to the name of the slots introduced into the lexical
environment.

            
Use:        (DEFINE-WITH-STRUCTURE structure-name slot1 slot2 ...)
instead of: (DEFSTRUCT structure-name slot1 slot2 ...)
to have a macro WITH-structure-named defined that you can as:

    (WITH-structure-name v
        (sqrt (* x x) (* y y)))

It's not polymorphic!


 
(DEFMACRO DEFINE-WITH-STRUCTURE (NAME-AND-OPTIONS &REST SLOTS)
  "
NAME-AND-OPTIONS:  Either a structure name or a list (name . options).
          Valid options are: (:conc-name prefix).
DO:       Define a macro: (WITH-{NAME} object &body body)
          expanding to a symbol-macrolet embedding body where
          symbol macros are defined to access the slots.
"
  (LET* ((NAME      (IF (SYMBOLP NAME-AND-OPTIONS)
                      NAME-AND-OPTIONS (CAR NAME-AND-OPTIONS)))
         (CONC-NAME (IF (SYMBOLP NAME-AND-OPTIONS)
                      (CONCATENATE 'STRING (STRING NAME) "-")
                      (LET ((CONC-OPT (CAR (MEMBER :CONC-NAME
                                                   (CDR NAME-AND-OPTIONS)
                                                   :KEY (FUNCTION CAR)))))
                        (IF CONC-OPT
                          (SECOND CONC-OPT)
                          (CONCATENATE 'STRING (STRING NAME) "-")))))
         (SLOT-NAMES (MAPCAR (LAMBDA (SLOT) (IF (LISTP SLOT) (CAR SLOT) SLOT)) 
                             SLOTS)))
    `(PROGN
       (DEFSTRUCT ,NAME-AND-OPTIONS ,@SLOTS)
       (DEFMACRO
         ,(INTERN (WITH-STANDARD-IO-SYNTAX (FORMAT NIL "WITH-~A" NAME)))
         (OBJECT &BODY BODY)
         (IF (SYMBOLP OBJECT)
           `(SYMBOL-MACROLET
             ,(MAPCAR
               (LAMBDA (SLOT)
                 (LIST SLOT
                       (LIST
                        (INTERN (WITH-STANDARD-IO-SYNTAX 
                                    (CONCATENATE 'STRING
                                      (STRING ',CONC-NAME) (STRING SLOT))))
                        OBJECT))) ',SLOT-NAMES)
             ,@BODY)
           (LET ((OBJV (GENSYM)))
             `(LET ((,OBJV ,OBJECT))
                (SYMBOL-MACROLET
                 ,(MAPCAR
                   (LAMBDA (SLOT)
                     (LIST SLOT
                           (LIST
                            (INTERN (WITH-STANDARD-IO-SYNTAX
                                        (CONCATENATE 'STRING
                                          (STRING ',CONC-NAME) (STRING SLOT))))
                            OBJV))) ',SLOT-NAMES)
                 ,@BODY))))))))



[9]> (macroexpand '(DEFINE-WITH-STRUCTURE (test (:type list)) a b (c 0)))
(PROGN (DEFSTRUCT (TEST (:TYPE LIST)) A B (C 0))
 (DEFMACRO WITH-TEST (OBJECT &BODY BODY)
  (IF (SYMBOLP OBJECT)
   (LIST* 'SYMBOL-MACROLET
    (MAPCAR
     (LAMBDA (SLOT)
      (LIST SLOT
       (LIST
        (INTERN
         (WITH-STANDARD-IO-SYNTAX
          (CONCATENATE 'STRING (STRING '"TEST-") (STRING SLOT))))
        OBJECT)))
     '(A B C))
    BODY)
   (LET ((OBJV (GENSYM)))
    (LIST 'LET (LIST (LIST OBJV OBJECT))
     (LIST* 'SYMBOL-MACROLET
      (MAPCAR
       (LAMBDA (SLOT)
        (LIST SLOT
         (LIST
          (INTERN
           (WITH-STANDARD-IO-SYNTAX
            (CONCATENATE 'STRING (STRING '"TEST-") (STRING SLOT))))
          OBJV)))
       '(A B C))
      BODY)))))) ;
T
[10]>  (DEFINE-WITH-STRUCTURE (test (:type list)) a b (c 0))
WITH-TEST
[11]> (macroexpand '(with-test e (print a)))
(SYMBOL-MACROLET ((A (TEST-A E)) (B (TEST-B E)) (C (TEST-C E))) (PRINT A)) ;
T
[12]> 


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
You never feed me.
Perhaps I'll sleep on your face.
That will sure show you.
From: pctech
Subject: Re: Getting slot names from struct instances
Date: 
Message-ID: <1110582580.479717.57590@g14g2000cwa.googlegroups.com>
I'm trying to get a laptop in time for church camp summer job. Please
help if you can by using my referal link:
http://www.pctech4free.com/def­ault.aspx?ref=59054

Thanks in advance.
From: Kalle Olavi Niemitalo
Subject: Re: Getting slot names from struct instances
Date: 
Message-ID: <87psy6li5s.fsf@Astalo.kon.iki.fi>
···········@netscape.net (Ulf Wikstr?m) writes:

> (defstruct vector x y)

VECTOR is already a standard type.

> (defun length (v)
>   (with-slot-names v
>     (sqrt (* x x) (* y y))))

That would be difficult to implement.  The problem is that the
type of V is not known at compile time, so the WITH-SLOT-NAMES
macro cannot know whether X and Y should refer to its slots or to
variables bound at some outer level.  I think the best expansion
you can get is something like:

  (if (slot-exists-p v 'x)
    (if (slot-exists-p v 'y)
      (with-slots (x y) v
        (sqrt (* x x) (* y y)))
      (with-slots (x) v
        (sqrt (* x x) (* y y)))
    (if (slot-exists-p v 'y)
      (with-slots (y) v
        (sqrt (* x x) (* y y)))
      (with-slots () v
        (sqrt (* x x) (* y y))))))

WITH-SLOT-NAMES would have to walk the body to see that it uses
the variables X and Y, and then generate this tree.

However, WITH-SLOTS is not guaranteed to work with structures.
SLOT-EXISTS-P is, though.

> But how do I get the slot names from v (if possible)?

At run time, you may be able to do this with the Metaobject
Protocol, although that is not standard CL and again might
require DEFCLASS instead of DEFSTRUCT.  The MOP may also reveal
slots that the implementation has added for its own purposes, but
those should have names that can't conflict with your symbols.

The other option is to wrap DEFSTRUCT inside your own macro that
stashes the list of slots and their associated reader and writer
functions somewhere accessible, perhaps to a property of the
symbol that is the proper name of the structure type.  Then get
that symbol with TYPE-OF.  Because DEFSTRUCT may implement
setfability of slots with setf expanders rather than setf
functions, you'd need to define your own writer functions,
perhaps with LAMBDA.
From: Alan Crowe
Subject: Re: Getting slot names from struct instances
Date: 
Message-ID: <86is3y2aug.fsf@cawtech.freeserve.co.uk>
ulfwikstrom asked
> I'd like to be able to do something like:
>
> (defstruct vector x y)
>
> (defun length (v)
>   (with-slot-names v
>     (sqrt (* x x) (* y y))))
>
> But how do I get the slot names from v (if possible)?

An easy version, putting a wrapper around defstruct goes
something like this:

(defmacro defstructure (name &rest fields)
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     ;; stash the field names 
     (setf (get ',name 'structure-fields) ',fields)
     (defstruct ,name ,@fields)))

(defmacro with-fields (name struct &body code)
  (let ((struct-holder (make-symbol "STRUCT")))
     `(let ((,struct-holder ,struct))
	(check-type ,struct-holder ,name)
	(symbol-macrolet
	 ,(mapcar
	   (lambda (field)
	     (list field
		   (list (intern (format nil "~A-~A"
					 name
					 field))
			 struct-holder)))
	   (get name 'structure-fields))
	 ,@code))))

(defstructure v x y)

(defvar *v* (make-v :x 5 :y 7))

(with-fields v *v*
	       (print (+ x y))
	       (print (* x y)))

CL-USER(1): (compile-file "structure")
CL-USER(2): (load "structure")
; Fast loading /home/alan/lisp/for-c.l.l./structure.fasl

12 
35 
T

Spot the cheat - with-fields is given the unevaluated name
of the structure and sets up x and y for that particular
structure only. That is what check-type is for. If, at
run-time, the structure turns out to be a different type
with-fields signals an error.

I reckon that restriction is likely to be acceptable,
reasoning that you have to make up your mind which structure
to use at type-in time not run-time, else you would not know
what names to use to refer to fields of the structure.

I'm not sure about the INTERN. I suspect it needs a package
argument, perhaps (symbol-package name) to make sure that 
my generated v-x is in the same package (and thus the same
symbol) as the v-x made by defstruct.

I wonder if I've really got the evil-when right. No eldritch
screeching so far ....

Alan Crowe
Edinburgh
Scotland
From: Pascal Bourguignon
Subject: Re: Getting slot names from struct instances
Date: 
Message-ID: <87sm31jaih.fsf@thalassa.informatimago.com>
Alan Crowe <····@cawtech.freeserve.co.uk> writes:

> ulfwikstrom asked
> > I'd like to be able to do something like:
> >
> > (defstruct vector x y)
> >
> > (defun length (v)
> >   (with-slot-names v
> >     (sqrt (* x x) (* y y))))
> >
> > But how do I get the slot names from v (if possible)?
> 
> An easy version, putting a wrapper around defstruct goes
> something like this:
> 
> (defmacro defstructure (name &rest fields)
>   `(eval-when (:compile-toplevel :load-toplevel :execute)
>      ;; stash the field names 
>      (setf (get ',name 'structure-fields) ',fields)
>      (defstruct ,name ,@fields)))
> 
> (defmacro with-fields (name struct &body code)
>   (let ((struct-holder (make-symbol "STRUCT")))
>      `(let ((,struct-holder ,struct))
> 	(check-type ,struct-holder ,name)
> 	(symbol-macrolet
> 	 ,(mapcar
> 	   (lambda (field)
> 	     (list field
> 		   (list (intern (format nil "~A-~A"
> 					 name
> 					 field))
> 			 struct-holder)))
> 	   (get name 'structure-fields))
> 	 ,@code))))
> 
> (defstructure v x y)
> 
> (defvar *v* (make-v :x 5 :y 7))
> 
> (with-fields v *v*
> 	       (print (+ x y))
> 	       (print (* x y)))

In defstructure, you should parse the name since it may be a list with
options and in particular with a CONC-NAME.

Needing to cite the type of the variable is ugly.  You could use
(type-of struct-holder) in with-fields instead of having to check-type.
But this would not work for a (defstructure (bad (:type list)) ...),
that's why I did not try it.

The INTERNing could be done in defstructure and indeed a package in
needed.  In defstructure you can leave the default package *package*,
but in with-fields you could be in a different package from where the
structure was defined.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Cats meow out of angst
"Thumbs! If only we had thumbs!
We could break so much!"