Newsgroups: comp.lang.lisp
Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!zaphod.mps.ohio-state.edu!think.com!barmar
From: barmar@think.com (Barry Margolin)
Subject: Re: Accessing a field of a Lisp structure
Message-ID: <1991Apr16.062652.15944@Think.COM>
Sender: news@Think.COM
Organization: Thinking Machines Corporation, Cambridge MA, USA
References: <1991Apr11.215248.9075@cbnewsl.att.com> <1991Apr14.073241.23653@Think.COM> <1991Apr14.181950.21605@cbnewsl.att.com>
Distribution: usa
Date: Tue, 16 Apr 91 06:26:52 GMT

In article <1991Apr14.181950.21605@cbnewsl.att.com> davel@cbnewsl.att.com (David Loewenstern) writes:
>Exactly. So the version with Find-Symbol, when run, will generate a 
>TEST-NIL error, and the version with INTERN, when run, will generate a
>TEST-<mumble> error.  Test-<mumble> is of course more useful for debugging,
>although either method is lazier and less preferable than placing
>explicit error-checking code in the macro definition.

In the functional case, the call to ACCESS-FIELD will be on the stack, so
it's not hard to tell which erroneous field name was specified.  But I
admit that the difference between using FIND-SYMBOL and INTERN for this
kludge is not really that important; I was only being complete, and trying
to find as many things wrong with the INTERN/FORMAT version as I could.

>Ahh, now I see your confusion.  The poster I responded to wasn't the one
>who wanted a function.  He said:
>
>>>>I would like to wite a macro 'access-field' that I can invoke
>>>>something like  
>>>>
>>>>      (access-field instance 'a)

>Since the poster specifically asked for a macro, not a function, I
>assumed the quote was to be considered syntactic sugar placed there
>for consistency with function syntax.

First of all, I was responding to a different posting.  But even if I had
responded to the above posting, I probably would have assumed the poster
had misused the term "macro", and meant "function", since he quoted the
slot name.  Even if he did want a macro, I still would have assumed that
the slot name argument is intended to be evaluated at runtime.

>As far as utility, I can think of a reason:
>unity of syntax between function and macro, coupled with a need
>for speed of access, as in:
>(defmacro access-field (struc slot)
>  (if (OR (ATOM slot) (EQ (FIRST slot) 'QUOTE))
>      `(,(intern (format nil "~a-~a" 'test
>                         (IF (ATOM slot) slot (SECOND slot)))
>        ,struc)
>      `(access-field-function ,struc ,slot)))  

I don't like the special-casing of (ATOM slot) in this.  It screws up:

(let ((slot-name (prompt-user-for-slot-name)))
  (access-field structure slot-name))

This will end up trying to access the slot named "slot-name".  A better
version would be:

(defmacro access-field (struc slot)
  (if (constantp slot)
      `(,(intern (format nil "~a-~a" 'test (eval slot)) 'test-pkg)
        ,struc)
      `(access-field-function ,struc ,slot)))

>I have actually seen this done in a real program.  

And I'll bet they usually forget to include the second argument to INTERN.
This is necessary to allow the invocation of the macro to be in a different
package from the structure definition.

Note that the macro could use CASE at expansion time, analogously with my
functional version:

(defmacro access-field (struc slot)
  (if (constantp slot)
      `(,(ecase (eval slot)
	   (a 'test-a)
	   (b 'test-b)
	   (c 'test-c))
        ,struc)
      `(access-field-function ,struc ,slot)))


>						    Why?  So tools
>like the following could be created:
>
>(defmacro most-recent (struc slot) `(CDAR (access-field ,struc ,slot)))
>(defmacro value-at-time (struc slot time)
>  `(CDR (ASSOC ,time (access-field ,struc ,slot))))
>and so forth.

The few times I've wanted macros like this, I've passed the entire accessor
name as a parameter, so that the macro isn't tied to a particular structure
type.  Or I write functions and pass #'<accessor> as a parameter.  Contrary
to what some Schemers believe, not all CLers avoid funargs.

>The CASE method
>is a bit of a pain if the structure is still under development,

One thing I've been meaning to mention is that you could write a macro that
expands into a DEFSTRUCT invocation followed by a definition of a generic
field accessor for the structure.  The slot description arguments to the
new macro would be used to define the generic field accessor.  Then you
don't have to update two places when you add or delete fields.  Something
like:

(defun defstruct-with-generic-accessor (name-and-options &body slots)
  `(progn
     (defstruct ,name-and-options .,slots)
     ,(let* ((name (if (atom name-and-options)
		       name-and-options
		       (car name-and-options)))
	     (options (and (listp name-and-options)
			   (cdr name-and-options)))
	     (conc-name (assoc :conc-name options))
	     (slot-names (mapcar #'(lambda (slot)
				     (if (atom slot)
					 slot
					 (car slot)))
				 slots)))
	(setq conc-name
	      (cond ((null conc-name) (format nil "~a-" name))
		    ((second (conc-name)))
		    (t "")))
	`(defun ,(intern (format nil "ACCESS-~a" name)) (,name slot)
	   (ecase slot
	     .,(mapcar #'(lambda (slot-name)
			   `(,slot-name (,(intern (format nil "~a~a"
						          conc-name slot-name))
				    slot)))
		       slot-names))))))
--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar
