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

Question about Lisp Macros

7 views
Skip to first unread message

Travis C. Porco

unread,
Dec 20, 1997, 3:00:00 AM12/20/97
to

Dear Readers--

Many of us are occasionally puzzled by the behavior of Lisp macros.
For instance, I recently was puzzled by a macro which seems to work
when called from the top level, but which does not work properly when
called from within a function. Here is the code block:

(defstruct point x y)
(setf p1 (make-point :x 0 :y 0))
(defmacro change (inp ex newval)
(let ((g (gensym)))
`(let ((,g (copy-point ,inp)))
(setf (,ex ,g) ,newval)
,g)))
(defun chf (inp1 ex1 ne1)
(change inp1 ex1 ne1))

The idea is that the argument ex to the macro change will be the name
of a structure slot accessor.

But here is what happened:
$ acl
Allegro CL 4.3 [Linux/X86; R1] (12/11/96 1:33)
Copyright (C) 1985-1996, Franz Inc., Berkeley, CA, USA. All Rights Reserved.
;; Optimization settings: safety 1, space 1, speed 1, debug 2.
;; For a complete description of all compiler switches given the
;; current optimization settings evaluate (EXPLAIN-COMPILER-SETTINGS).
USER(1): (load "test.l")
; Loading ./test.l
T
USER(2): (change p1 point-x 2)
#S(POINT :X 2 :Y 0)
; the macro change is working fine, but--
USER(3): (chf p1 'point-x 2)
Error: (SETF EX1) does not have a function definition
[1] USER(4): :pop

Somehow, the formal parameter ex1 of the function chf does not get set
to point-x. Why does the macro change work fine from the top level,
but behave strangely when called from a function?

Perhaps some of you who are more experienced with Lisp can help answer
this question. Macros provide a considerable amount of power in Lisp, but
it's always tempting to avoid them to avoid this sort of behavior, so
frustrating to a beginning writer of macros.

BTW, this block of code is a small abstraction of the actual code that
I'm working on, designed to exhibit the problem in a concise way
appropriate for a newsgroup. Also, there are plenty of obvious workarounds
and alternative solutions, so that's not the problem.

I posted the question because it seemed like a macro that can't be called
from a function ought to be understood, and that the answer to this
puzzle might be interesting to other beginners like myself. Please
send any flames of the form "you idiot, anyone knows that..." to
/dev/null as usual.

Many thanks for any help.

Kind regards, --Travis Porco

Barry Margolin

unread,
Dec 21, 1997, 3:00:00 AM12/21/97
to

In article <67hl2g$n8g$1...@agate.berkeley.edu>,

Travis C. Porco <po...@stat.Berkeley.EDU> wrote:
>(defmacro change (inp ex newval)
> (let ((g (gensym)))
> `(let ((,g (copy-point ,inp)))
> (setf (,ex ,g) ,newval)
> ,g)))
>(defun chf (inp1 ex1 ne1)
> (change inp1 ex1 ne1))
>
>USER(3): (chf p1 'point-x 2)
>Error: (SETF EX1) does not have a function definition
>[1] USER(4): :pop
>
>Somehow, the formal parameter ex1 of the function chf does not get set
>to point-x. Why does the macro change work fine from the top level,
>but behave strangely when called from a function?

The arguments to a macro are the subexpressions of the form in which it
appears, *not* evaluated. So in the function CHF, the macro CHANGE is
being called with the arguments INP1, EX1, and NE1. Macro substitution is
normally done by the compiler, so your function is equivalent to:

(defun chf (inp1 ex1 ne1)

(let ((#:g001 (copy-point inp1)))
(setf (ex1 #:g001) ne1)
#:g001))

You can see what a macro will expand to using the MACROEXPAND function.

>I posted the question because it seemed like a macro that can't be called
>from a function ought to be understood, and that the answer to this
>puzzle might be interesting to other beginners like myself. Please
>send any flames of the form "you idiot, anyone knows that..." to
>/dev/null as usual.

The reason you had to use a macro in the first place is because you wanted
to substitute into the operator position of the "place" form of a SETF, and
this can't be done when the operator isn't known until run time. Well,
your macro allows the operator to be supplied by the macro invocation, but
it still can't be supplied at run time.

--
Barry Margolin, bar...@bbnplanet.com
GTE Internetworking, Powered by BBN, Cambridge, MA
Support the anti-spam movement; see <http://www.cauce.org/>
Please don't send technical questions directly to me, post them to newsgroups.

Erik Naggum

unread,
Dec 21, 1997, 3:00:00 AM12/21/97
to

* Travis C. Porco

| Many of us are occasionally puzzled by the behavior of Lisp macros.

uhm, that introduction left me thinking that it is a bad idea to respond.

| For instance, I recently was puzzled by a macro which seems to work
| when called from the top level, but which does not work properly when
| called from within a function. Here is the code block:
|
| (defstruct point x y)
| (setf p1 (make-point :x 0 :y 0))

| (defmacro change (inp ex newval)
| (let ((g (gensym)))
| `(let ((,g (copy-point ,inp)))
| (setf (,ex ,g) ,newval)
| ,g)))
| (defun chf (inp1 ex1 ne1)
| (change inp1 ex1 ne1))
|

| The idea is that the argument ex to the macro change will be the name
| of a structure slot accessor.

your macro `change' is naive, but working. your function `chf' is just
hopeless. it is not, as you seem to think, that macros "can't be called
from a function" that is confusing you, you have yet to understand when
and how evaluation of arguments take place, and macros are the least of
your problems at this time.

in `chf', you introduce three new variables, inp1, ex1, and ne1. when you
called (change p1 point-x 2), you gave `change' three _literals_ to work
on, but in (change inp1 ex1 nei1), you give `change' three _variables_
whose _values_ you want it to work on. how, pray tell, should `change'
know that you had changed your mind? or anybody else for that matter? on
top of that, you quote _one_ of the values in the call to `chf', but not
another. how did you _ever_ believe this could work?

| Somehow, the formal parameter ex1 of the function chf does not get set
| to point-x.

yes, it does. (of course it does!) a bigger problem is that inp1 gets the
_value_ of p1, which is no longer a valid argument to `copy-point'.

| Macros provide a considerable amount of power in Lisp, but it's always
| tempting to avoid them to avoid this sort of behavior, so frustrating to
| a beginning writer of macros.

this line of reasoning is baffling. you try to do something you do not
understand how to do and then you get unexpected results (it is not clear
that your expectations are consistent, either), and then you go on to make
false claims about parts of the system that behave correctly since you get
an error message you fail to investigate, only to wind up _avoiding_ the
tools that don't comply with your wishful thinking? really, now.

I think the only course of action for you is to explain to yourself why you
thought this would work, not just type random code and see whether it works
or not. it is literally impossible to unwind your guesswork to the point
where a precise answer will help you, not the least because you don't seem
to realize that you _are_ guessing at random.

BTW, macros _are_ hard to write right. it takes a good grasp of the many
different evaluation times in Common Lisp to get them exactly right.

#\Erik
--
If you think this year is number 97, | Help fight MULE in GNU Emacs 20!
_you_ are not "Year 2000 Compliant". | http://sourcery.naggum.no/emacs/

Paul Krueger

unread,
Dec 23, 1997, 3:00:00 AM12/23/97
to

In article <67hl2g$n8g$1...@agate.berkeley.edu>, po...@stat.Berkeley.EDU (Travis C. Porco) writes:
|> Dear Readers--


|>
|> Many of us are occasionally puzzled by the behavior of Lisp macros.

|> For instance, I recently was puzzled by a macro which seems to work
|> when called from the top level, but which does not work properly when
|> called from within a function. Here is the code block:
|>
|> (defstruct point x y)
|> (setf p1 (make-point :x 0 :y 0))
|> (defmacro change (inp ex newval)
|> (let ((g (gensym)))
|> `(let ((,g (copy-point ,inp)))
|> (setf (,ex ,g) ,newval)
|> ,g)))
|> (defun chf (inp1 ex1 ne1)
|> (change inp1 ex1 ne1))
|>
|> The idea is that the argument ex to the macro change will be the name
|> of a structure slot accessor.

Macros are among the more difficult features of lisp, but once you figure out
what gets evaluated when, you shouldn't have much trouble. First remember that
arguments given to macros are not evaluated before being given to the macro
function.

So when you make the change call within chf, the arguments used in
the macro expansion are 'inp1 'ex1 and 'ne1. Often macro arguments are used as
function arguments in the return form and therefore will get evaluated when the
returned form is evaluated. But in your case, the form returned to chf contains
"(setf (ex inp1) ne1)" and setf is a macro that doesn't evaluate its first argument,
hence the error.

If you want a macro argument to be evaluated, as you obviously do here, then you
have to arrange for the form returned from the macro to do it. Something more like
the following:

(defmacro change (inp ex newval)
(let ((g (gensym))

(f (gensym)))
`(let ((,g (copy-point ,inp))
(,f (eval (list 'function (list 'setf ,ex)))))
(funcall ,f ,newval ,g)
,g)))

Now the call at the top level must be: (change p1 'point-x 2) because we have
set things up so that the ex argument is evaluated in the returned form. Your
chf function can stay the same.

There are some other subtle points about this particular example. In general, the
use of "eval" in a returned form is usually a warning to me that I might have
missed something. Remember that eval uses the global environment. In this
case the form being eval'ed is (function (setf point-x)) which is well-defined
in the global environment. Note that I didn't eval the whole setf form because
I might well have used some variable local to chf in the macro call (as the new
value for example) and that would need to be expanded within chf's environment.
If the setf function for the accessor itself (e.g. the setf function for point-x)
was defined only in the local environment, then this version of change wouldn't
work. It's possible to create a version that will, but we'll leave that as an
advanced exercise for the student.

By the way, you can use the macroexpand-1 function to see what form your macro
returns (that form is evaluated in the calling environment), and that can help you
understand what is going on.

|>
|> But here is what happened:
|> $ acl
|> Allegro CL 4.3 [Linux/X86; R1] (12/11/96 1:33)
|> Copyright (C) 1985-1996, Franz Inc., Berkeley, CA, USA. All Rights Reserved.
|> ;; Optimization settings: safety 1, space 1, speed 1, debug 2.
|> ;; For a complete description of all compiler switches given the
|> ;; current optimization settings evaluate (EXPLAIN-COMPILER-SETTINGS).
|> USER(1): (load "test.l")
|> ; Loading ./test.l
|> T
|> USER(2): (change p1 point-x 2)
|> #S(POINT :X 2 :Y 0)
|> ; the macro change is working fine, but--

|> USER(3): (chf p1 'point-x 2)
|> Error: (SETF EX1) does not have a function definition
|> [1] USER(4): :pop
|>

|> Somehow, the formal parameter ex1 of the function chf does not get set

|> to point-x. Why does the macro change work fine from the top level,
|> but behave strangely when called from a function?
|>

|> Perhaps some of you who are more experienced with Lisp can help answer

|> this question. Macros provide a considerable amount of power in Lisp, but


|> it's always tempting to avoid them to avoid this sort of behavior, so
|> frustrating to a beginning writer of macros.
|>

|> BTW, this block of code is a small abstraction of the actual code that
|> I'm working on, designed to exhibit the problem in a concise way
|> appropriate for a newsgroup. Also, there are plenty of obvious workarounds
|> and alternative solutions, so that's not the problem.
|>

|> I posted the question because it seemed like a macro that can't be called
|> from a function ought to be understood, and that the answer to this
|> puzzle might be interesting to other beginners like myself. Please
|> send any flames of the form "you idiot, anyone knows that..." to
|> /dev/null as usual.
|>

|> Many thanks for any help.
|>
|> Kind regards, --Travis Porco

--
Paul Krueger, Ph.D. Cray Research Park
pkru...@cray.com 655F Lone Oak Drive
(612) 683-5243 Fax: (612) 683-5889 Eagan, MN 55121 USA

Barry Margolin

unread,
Dec 23, 1997, 3:00:00 AM12/23/97
to

In article <67mv1h$27o$1...@walter.cray.com>,

Paul Krueger <pkru...@cray.com> wrote:
>There are some other subtle points about this particular example. In general, the
>use of "eval" in a returned form is usually a warning to me that I might have
>missed something. Remember that eval uses the global environment. In this
>case the form being eval'ed is (function (setf point-x)) which is well-defined
>in the global environment. Note that I didn't eval the whole setf form because
>I might well have used some variable local to chf in the macro call (as the new
>value for example) and that would need to be expanded within chf's environment.
>If the setf function for the accessor itself (e.g. the setf function for point-x)
>was defined only in the local environment, then this version of change wouldn't
>work. It's possible to create a version that will, but we'll leave that as an
>advanced exercise for the student.

There's another problem with your macro. There's no guarantee that
(function (setf point-x)) is defined. There are two ways to define a SETF
place form: defining a SETF expander with DEFSETF or DEFINE-SETF-EXPANDER
(formerly DEFINE-SETF-METHOD) or defining a function named (SETF <symbol>).
ANSI CL specifically says:

it is implementation-dependent whether the ability to write the slot is
implemented by a setf function or a setf expander.

Your macro will only work in implementations that define a setf function.

0 new messages