[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Collecting items



Let's start at the end of your message, where you complain that it
doesn't "work" to modify a quoted constant.  It's not supposed to work,
and depending on it is non-portable.  See p. 115 of CLtL2.  As for the
wording of the error message, (about a list embedded in a structure) it
has to do with the details of the implementation of lists and compiled
functions in the Symbolics architecture.  I admit that these details
should not be "flashed" at the user.  But the bottom line is that
Symbolics is in strict conformance with ANSI on this one.

The correct idiom is 

(let* ((accum (list 'junk)) (point accum)) ...)

Let's move backward to your comments about uninterned symbols in the
expansion of macros.  You seem to be under the impression that the
variables with uninterned names are global; they aren't.  They are
introduced using the standard local-variable constructs LET and PROG.
So all the GENSYM'ed variables are in fact local.

The reason they are uninterned has nothing to do with scope.  It's bad
style for a macro to introduce an invisible local variable with an
interned name, because the programmer could unwittingly use that same
name in his code.

For example, suppose we had a macro called REPEAT that worked like this:

[1] (REPEAT 5 (PRINC "HI "))HI HI HI HI HI 

How would you define it?  One's first impulse is something like

[2] (DEFMACRO REPEAT (COUNT &BODY BODY)
     `(LET ((VAR 0))
        (LOOP (WHEN (>= VAR ,COUNT) (RETURN))
              ,@BODY
              (INCF VAR))))

This would cause the example [1] to expand into

[3] (LET ((VAR 0))
      (LOOP (WHEN (>= VAR 5) (RETURN))
            (PRINC "HI ")
            (INCF VAR)))

which looks OK.  But what if we try another example:

[4] (LET ((VAR 6)) (REPEAT 5 (PRINC VAR)))

Here the programmer expects to see 66666 but sees 01234 instead.  The
macro introduced an invisible variable that overrode the original one,
leading to an undesirable "surprise".  To be clean and safe, if a macro
introduces a new local variable into an environment to which the user
can add code, that variable must have an inaccessible -- that is, an
uninterned -- name.  (Sorry about my tangled grammar.)

A more correct implementation would be:

[5] (DEFMACRO REPEAT (COUNT &BODY BODY)
      (LET ((VAR (GENSYM)))
        `(LET ((,VAR 0))
           (LOOP (WHEN (>= ,VAR ,COUNT) (RETURN))
                 ,@BODY
                 (INCF ,VAR)))))

Even this version is subject to a subtle "once-only" problem regarding
the parameter COUNT.  Consider this screwy example:

[6] (REPEAT (READ) (PRINC "HI "))

Here the obvious intent is to read a single number from the user and
print HI that number of times.  But instead, it will read every time
around the loop!  Think about this, and you'll see why the macro really
has to be:

[7] (DEFMACRO REPEAT (COUNT &BODY BODY)
      (LET ((VAR (GENSYM))
            (CVAR (GENSYM)))
        `(LET ((,VAR 0)
               (,CVAR ,COUNT))
           (LOOP (WHEN (>= ,VAR ,CVAR) (RETURN))
                 ,@BODY
                 (INCF ,VAR)))))

The bottom line is, macros are hard to get right; if you see something
you don't understand in a macro-expansion, think long and hard before
you decide it's due to idiocy!