From: Jeff Dalton
Subject: static, a.k.a. eval-once
Date: 
Message-ID: <7399@skye.ed.ac.uk>
About 4 years ago, I wrote an EVAL-ONCE macro.  It appears, with
an explanation below.  It has been claimed that my macro isn't
portable because the behavior of gensyms isn't sufficiently
restricted when compile and load is involved.  What I want to
know is:

  1. Is there a problem with using GENSYM this way?

  2. Is there a better way to get what I want.

  3. Is there a way that will get the EVAL-ONCE effect in
     interpreted code even in an "expand macros each time"
     implementation?  (N.B. There is no correct portable way
     to use *macroexpand-hook* cache macro expansions --
     see page 204 of CLtL II.)

The "better way" will almost certainly involve LOAD-TIME-VALUE.
But note that simply using LOAD-TIME-VALUE instead of EVAL-ONCE
is not enough, because it might be that the form can't be evaluated
at load time.  Using LOAD-TIME-VALUE to create a "cell" that
can be modified to contain the desired value looks like the best
possibility.

For the interpreted code problem, I can't think of anything better
than having EVAL-ONCE assume that EQ forms are never logically
different expressions.

Anyway, here is my old code:

----------------------------------------------------------------------
;;; Oh, let us try this dubious trick...
;;; What we want is sort of like 'static'...
;;; Some code appears once but is executed again and again.
;;; Some value is computed each time the code is executed, but
;;; we would like it to be computed once and reused thereafter.
;;; (The value can't be computed at compile or load time, perhaps.)
;;; Each time this macro appears (modulo re-expansions), it
;;; creates a symbol that is ubound the first time the code
;;; is executed and has the desired value thereafter.

(defmacro eval-once (form)
  `(let ((v ',(gensym)))		;the symbol persists between calls
     (if (boundp v)
	 (symbol-value v)
       (set v ,form))))

#+maybe
(defmacro eval-once (form)
  (let ((v (gensym)))
    `(locally (declare (special ,v))
       (if (boundp ',v)
           ,v
         (setq ,v ,form)))))
----------------------------------------------------------------------

-- jd

From: Barry Margolin
Subject: Re: static, a.k.a. eval-once
Date: 
Message-ID: <17u2ikINNs6g@early-bird.think.com>
In article <····@skye.ed.ac.uk> ····@aiai.ed.ac.uk (Jeff Dalton) writes:
>About 4 years ago, I wrote an EVAL-ONCE macro.  It appears, with
>an explanation below.  It has been claimed that my macro isn't
>portable because the behavior of gensyms isn't sufficiently
>restricted when compile and load is involved.  What I want to
>know is:
>
>  1. Is there a problem with using GENSYM this way?

I think it should work.  While the compiler has to arrange for all
references to a particular gensym within a binary file to be the object,
similarly named gensyms in different binary files should refer to different
instances.

BTW, I assume you realize that V should be a gensym, i.e. it should be:

I think your version can be simplified a bit.  The expansion doesn't need a
LET binding:

(defmacro eval-once (form)
  (let ((var (gensym)))
    `(if (boundp ',var)
	 (symbol-value ',var)
	 (set ',var ,form))))

>  2. Is there a better way to get what I want.

Not that I can think of quickly (I thought of using something like Scheme's
delay/force, but it didn't work).

This looks suspiciously like the stuff we were discussing a few months ago,
where you wanted the ability to have modifiable literals in your program.
If that were allowed, you could do:

(defstruct eval-once
  (done nil)
  value)

(defmacro eval-once (form)
  (let ((struct (make-eval-once)))
    `(if (eval-once-done ',struct)
	  (eval-once-value ',struct)
	  (setf (eval-once-done ',struct) t
		(eval-once-value ',struct) ,form)))))

>  3. Is there a way that will get the EVAL-ONCE effect in
>     interpreted code even in an "expand macros each time"
>     implementation?  (N.B. There is no correct portable way
>     to use *macroexpand-hook* cache macro expansions --
>     see page 204 of CLtL II.)
...
>For the interpreted code problem, I can't think of anything better
>than having EVAL-ONCE assume that EQ forms are never logically
>different expressions.

I can't think of another way, either.
-- 
Barry Margolin
System Manager, Thinking Machines Corp.

······@think.com          {uunet,harvard}!think!barmar
From: Jeff Dalton
Subject: Re: static, a.k.a. eval-once
Date: 
Message-ID: <7412@skye.ed.ac.uk>
In article <············@early-bird.think.com> ······@think.com (Barry Margolin) writes:
>In article <····@skye.ed.ac.uk> ····@aiai.ed.ac.uk (Jeff Dalton) writes:
>>About 4 years ago, I wrote an EVAL-ONCE macro.  It appears, with
>>an explanation below.  It has been claimed that my macro isn't
>>portable because the behavior of gensyms isn't sufficiently
>>restricted when compile and load is involved.  What I want to
>>know is:
>>
>>  1. Is there a problem with using GENSYM this way?
>
>I think it should work.  While the compiler has to arrange for all
>references to a particular gensym within a binary file to be the object,
>similarly named gensyms in different binary files should refer to different
>instances.
>
>BTW, I assume you realize that V should be a gensym, i.e. it should be:

Yes.  I'm not sure how I could have missed that.

>I think your version can be simplified a bit.  The expansion doesn't need a
>LET binding:
>
>(defmacro eval-once (form)
>  (let ((var (gensym)))
>    `(if (boundp ',var)
>	 (symbol-value ',var)
>	 (set ',var ,form))))

Both this and the V problem were fixed in the "#+maybe" version:

#+maybe
(defmacro eval-once (form)
  (let ((v (gensym)))
    `(locally (declare (special ,v))
       (if (boundp ',v)
           ,v
         (setq ,v ,form)))))

I suspect (it was years ago) that what happened was that I started
with something like your version, modified it (carelessly) to get
around a bug in KCL, and then wrote the "#+maybe" version w/o realizing
that it fixed the bug involving V.

The bug in KCL was that if a gensym appeared more than once _as data_
(eg, as a quoted symbol in two different places), they would end up as
separate symbols after a compile/load.  (This bug has now been fixed,
I believe.)  However, the "#+maybe" has the problem that the uses
of comma-V as a variable and as data have to line up, which wouldn't have
worked then in KCL (I suspect).

>This looks suspiciously like the stuff we were discussing a few months ago,
>where you wanted the ability to have modifiable literals in your program.
>If that were allowed, you could do:
>
>(defstruct eval-once
>  (done nil)
>  value)
>
>(defmacro eval-once (form)
>  (let ((struct (make-eval-once)))
>    `(if (eval-once-done ',struct)
>	  (eval-once-value ',struct)
>	  (setf (eval-once-done ',struct) t
>		(eval-once-value ',struct) ,form)))))

Well, I do want to have _some_ way to do this EVAL-ONCE trick.

You're right to suspect that this is connected to our earlier
discussion.  I first brought up the static/eval-once issue on one of
the Common Lisp mailing lists shortly after I'd written my macro (ie,
some years back).  If I recall correctly, what was said then was that
gensyms weren't reliable enough but that LOAD-TIME-VALUE might do it
(somehow).  So I've had it in mind to figure out, sooner or later,
whether LOAD-TIME-VALUE did help.  Various things remind me of this --
such things as the discussion about modifying constants and (now) the
public review of the dpANS.  (The latter one works by making me try
to remember the things I was going to raise during public review,
which leads me to remember other things too.)

Anyway, modifying a quoted structure looks like one way to do
eval-once, and LOAD-TIME-VALUE looks like it might help either by
giving you a modifiable object (that isn't quoted) at load time or by
letting you modify a quoted object (because you use the 2nd argument
to LOAD-TIME-VALUE to _say_ it's modifiable.  So, using the struct
you defined, we might try:

(defmacro eval-once (form)
  (let ((v (gensym)))
    `(let ((,v (load-time-value (make-eval-once))))
       (if (eval-once-done ,v)
           (eval-once-value ,v)
         (setf (eval-once-done ,v) t
               (eval-once-value ,v) ,form)))))

But that's not enough in interpreted code.

-- jd