Forum: CompilerIssue: LOAD-TIME-EVAL
References: #, (p. 356), (EVAL-WHEN (LOAD) ...) (p. 69-70)
issue SHARP-COMMA-CONFUSION
Category: ADDITION
Edit history: 06-Jun-87, Version 1 by James Kempf
17-Jul-87, Version 2 by James Kempf
12-Nov-87, Version 3 by Pitman (alternate direction)
01-Feb-88, Version 4 by Moon
(from version 2 w/ edits suggested by Masinter)
06-Jun-88, Version 5 by Pitman
(fairly major overhaul, merging versions 3 and 4)
21-Sep-88, Version 6 by Moon (stripped down)
17-Oct-88, Version 7 by Loosemore (change direction again)
30-Dec-88, Version 8 by Loosemore (tweaks)
23-Jan-89, Version 9 by Loosemore (amendments)
02-Mar-89, Version 10 by Loosemore (new proposal)
11-Mar-89, Version 11 by Loosemore
Problem description:
Common Lisp provides reader syntax (#,) which allows the programmer
to designate that a particular expression within a program is to be
evaluated early (at load time) but to later be treated as a constant.
Unfortunately, no access to this capability is available to programs
which construct other programs without going through the reader.
Some computations can be deferred until load time by use of EVAL-WHEN,
but since EVAL-WHEN must occur only at toplevel, and since the nesting
behavior of EVAL-WHEN is quite unintuitive, EVAL-WHEN is not a general
solution to the problem of load-time computation of program constants.
Proposal R**2-NEW-SPECIAL-FORM was approved at the January 1989
meeting. After the meeting, some additional suggestions were made that
have been incorporated into proposal R**3-NEW-SPECIAL-FORM. The sections
of the two proposals that differ are marked with change bars in the margin.
Proposal (LOAD-TIME-EVAL:R**2-NEW-SPECIAL-FORM):
Add a new special form, LOAD-TIME-VALUE, which has the following
contract:
LOAD-TIME-VALUE form &optional read-only-p [Special Form]
LOAD-TIME-VALUE provides a mechanism for delaying evaluation of <form>
until the expression is in the "runtime" environment.
If a LOAD-TIME-VALUE expression is seen by COMPILE-FILE, the compiler
| performs normal semantic processing such as macro expansion but
| arranges for the evaluation of <form> to occur at load time in a null
lexical environment, with the result of this evaluation then being
treated as an immediate quantity at run time. It is guaranteed that
the evaluation of <form> will take place only once when the file is
loaded, but the order of evaluation with respect to the "evaluation"
of top-level forms in the file is unspecified.
If a LOAD-TIME-VALUE expression appears within a function compiled
with COMPILE, the <form> is evaluated at compile time in a null lexical
environment. The result of this compile-time evaluation is treated as
an immediate quantity in the compiled code.
In interpreted code, <form> is evaluated (by EVAL) in a null
lexical environment, and one value is returned. Implementations which
implicitly compile (or partially compile) expressions passed to
EVAL may evaluate the <form> only once, at the time this
compilation is performed. This is intentionally similar to the
freedom which implementations are given for the time of expanding
macros in interpreted code.
| Note that, in interpreted code, there is no guarantee as to when
| evaluation of <form> will take place, or the number of times the
| evaluation will be performed. Since successive evaluations of the
| same LOAD-TIME-VALUE expression may or may not result in an evaluation
| which returns a "fresh" object, destructive side-effects to the
| resulting object may or may not persist from one evaluation to the
| next. It is safest to explicitly initialize the object returned by
| LOAD-TIME-VALUE, if it is later modified destructively.
| Implementations must guarantee that each reference to a
| LOAD-TIME-VALUE expression results in at least one evaluation of its
| nested <form>. For example,
| (DEFMACRO CONS-SELF (X)
| `(CONS ,X ,X))
| (CONS-SELF (LOAD-TIME-VALUE (COMPUTE-IT)))
| must perform two calls to COMPUTE-IT; although there is only one
| unique LOAD-TIME-VALUE expression, there are two distinct references
| to it.
|
| In the case of a LOAD-TIME-VALUE form appearing in a quoted expression
| passed to EVAL, each call to EVAL must result in a new evaluation of
| <form>. For example,
| (DEFVAR X 0)
| (DEFUN FOO () (EVAL '(LOAD-TIME-VALUE (INCF X))))
| is guaranteed to increment X each time FOO is called, while
| (DEFUN FOO () (LOAD-TIME-VALUE (INCF X)))
| may cause X to be evaluated only once.
The READ-ONLY-P argument designates whether the result can be considered
read-only constant. If NIL (the default), the result must be considered
ordinary, modifiable data. If T, the result is a read-only quantity
which may, as appropriate, be copied into read-only space and/or shared
with other programs. (Because this is a special form, this argument is
-not- evaluated and only the literal symbols T and NIL are permitted.)
Rationale:
LOAD-TIME-VALUE is a special form rather than a function or macro
because it requires special handling by the compiler.
Requiring the compiler to perform semantic processing such as macro
expansion on the nested <form>, rather than delaying all such processing
until load time, has the advantages that fewer macro libraries may need
to be available at load time, and that loading may be faster and result
in less consing due to macroexpansion. If users really want to delay
macroexpansion to load time, this can be done with an explicit call to
EVAL, e.g.
(LOAD-TIME-VALUE (EVAL '(MY-MACRO)))
Allowing the same LOAD-TIME-VALUE to cause its nested <form> to be
evaluated more than once makes simplifies its implementation in
interpreters which do not perform a preprocessing code walk. It also
makes the rules for the time of its processing analogous to those
for macro expansion.
This proposal explicitly does -not- tie LOAD-TIME-VALUE to the #,
read macro. Doing so would be an incompatible change to the definition
of #, (which is reliably useful only -inside- quoted structure,
while LOAD-TIME-VALUE must appear -outside- quoted structure in a
for-evaluation position).
The requirement that LOAD-TIME-VALUE expressions be evaluated once per
reference (rather than once per unique expression) prevents problems
that could result by performing destructive side-effects on a value
that is unexpectedly referenced in more than one place.
Proposal (LOAD-TIME-EVAL:R**3-NEW-SPECIAL-FORM):
Add a new special form, LOAD-TIME-VALUE, which has the following
contract:
LOAD-TIME-VALUE form &optional read-only-p [Special Form]
LOAD-TIME-VALUE provides a mechanism for delaying evaluation of <form>
until the expression is in the "runtime" environment.
If a LOAD-TIME-VALUE expression is seen by COMPILE-FILE, the compiler
| performs its normal semantic processing (such as macro expansion and
| translation into machine code) on the form, but arranges for the
| execution of <form> to occur at load time in a null
lexical environment, with the result of this evaluation then being
treated as an immediate quantity at run time. It is guaranteed that
the evaluation of <form> will take place only once when the file is
loaded, but the order of evaluation with respect to the "evaluation"
of top-level forms in the file is unspecified.
If a LOAD-TIME-VALUE expression appears within a function compiled
with COMPILE, the <form> is evaluated at compile time in a null lexical
environment. The result of this compile-time evaluation is treated as
an immediate quantity in the compiled code.
In interpreted code, <form> is evaluated (by EVAL) in a null
lexical environment, and one value is returned. Implementations which
implicitly compile (or partially compile) expressions passed to
EVAL may evaluate the <form> only once, at the time this
compilation is performed. This is intentionally similar to the
freedom which implementations are given for the time of expanding
macros in interpreted code.
| If the same (compared with EQ) list (LOAD-TIME-VALUE <form>) is
| evaluated or compiled more than once, it is unspecified whether <form>
| is evaluated only once or is evaluated more than once. This can
| happen both when an expression being evaluated or compiled shares
| substructure, and when the same expression is passed to EVAL or to
| COMPILE multiple times. Since a LOAD-TIME-VALUE expression may be
| referenced in more than one place and may be evaluated multiple times
| by the interpreter, it is unspecified whether each execution returns
| a "fresh" object or returns the same object as some other execution.
| Users must use caution when destructively modifying the resulting
| object.
|
| If two lists (LOAD-TIME-VALUE <form>) are EQUAL but not EQ, their
| values always come from distinct evaluations of <form>. Coalescing
| of these forms is not permitted.
The READ-ONLY-P argument designates whether the result can be considered
read-only constant. If NIL (the default), the result must be considered
ordinary, modifiable data. If T, the result is a read-only quantity
which may, as appropriate, be copied into read-only space and/or shared
with other programs. (Because this is a special form, this argument is
-not- evaluated and only the literal symbols T and NIL are permitted.)
Rationale:
LOAD-TIME-VALUE is a special form rather than a function or macro
because it requires special handling by the compiler.
Requiring the compiler to perform semantic processing such as macro
expansion on the nested <form>, rather than delaying all such processing
until load time, has the advantages that fewer macro libraries may need
to be available at load time, and that loading may be faster and result
in less consing due to macroexpansion. If users really want to delay
macroexpansion to load time, this can be done with an explicit call to
EVAL, e.g.
(LOAD-TIME-VALUE (EVAL '(MY-MACRO)))
Allowing the same LOAD-TIME-VALUE to cause its nested <form> to be
evaluated more than once makes simplifies its implementation in
interpreters which do not perform a preprocessing code walk. It also
makes the rules for the time of its processing analogous to those
for macro expansion.
This proposal explicitly does -not- tie LOAD-TIME-VALUE to the #,
read macro. Doing so would be an incompatible change to the definition
of #, (which is reliably useful only -inside- quoted structure,
while LOAD-TIME-VALUE must appear -outside- quoted structure in a
for-evaluation position).
Allowing multiple references to the same LOAD-TIME-VALUE expression
to result in only one interpretation allows it to be specified more
cleanly. It also allows interpreters that do not perform a prepass
to cache LOAD-TIME-VALUE expressions.
Current Practice:
This is an addition to the language and has not yet been implemented.
Cost to Implementors:
In compiled code, (LOAD-TIME-VALUE <form>) is similar to
'#,<form>. Most implementations can probably make use of the same
mechanism they use to handle #, to handle LOAD-TIME-VALUE. Note that
#, does not currently provide a mechanism for dealing with
non-read-only-ness.
Implementing LOAD-TIME-VALUE in the interpreter should be fairly
straightforward, since one simply needs to evaluate the <form> in the
null lexical environment. Implementations that use a preprocessing
code walk in the interpreter to perform macro expansion could process
LOAD-TIME-VALUE forms at that time.
Some code-walkers would have to be taught about this new
special form. Such changes would likely be trivial.
Cost to Users:
Some code-walkers would have to be taught about this new
special form. Such changes would likely be trivial.
Benefits:
Users are given a mechanism that to force evaluation to be delayed
until load time that does not rely on a feature of the reader.
Discussion:
Earlier versions (up to version 7) of this proposal stated that
all semantic processing of the LOAD-TIME-VALUE form should be postponed
until load time.
The semantics of LOAD-TIME-VALUE would be simplified considerably if
the READ-ONLY-P argument were removed and destructive operations on
the result of evaluating <form> prohibited. However, some people feel
that the ability to destructively modify the value is an essential
feature to include.
"Collapsing" of multiple references to the same LOAD-TIME-VALUE
expression could be allowed for read-only situations, but it seems
like it would be more confusing to make it legal in some situations
and not in others.
A number of other alternatives have been considered on this issue,
including:
- A proposal for a new special form that would force evaluation of
the <form> to happen only once. This was rejected because of
implementation difficulties.
- A proposal to add a function making the "magic cookie" used by #,
available to user code. The current proposal does not prevent such
a function from being added, but this approach appeared to have
less support than making the hook available as a new special form.
- A proposal to remove #, entirely (issue SHARP-COMMA-CONFUSION).
- A suggestion to change the behavior of (EVAL-WHEN (LOAD) ...).
Kent Pitman says:
Although I'm willing to take multiple evaluation in the interpreter
as a compromise position, I would like it mentioned in the discussion
that this was only an expedient to getting this issue accepted at all,
and that I'm not really happy about it. I have said that I think a
number of our lingering problems (with EVAL-WHEN, COMPILER-LET, and
this -- for example) are due to the presence of interpreters which do
not do a semantic-prepass at a known time. If I had my way, we would
require a semantic pre-pass and we would then be able to forbid
multiple evaluations even in the interpreter.
Moon and Gray support proposal R**3-NEW-SPECIAL-FORM.
Pitman also expressed willingness to go along with
R**3-NEW-SPECIAL-FORM, but was somewhat concerned that coalescing
LOAD-TIME-VALUE results based on EQ-ness of the LOAD-TIME-VALUE form
could conceivably lead to trouble down the line. However, since he
could provide no actual examples to back up that worry, and since the
majority opinion was that some implementations would find a
restriction against such coalescing an undue burden, the decision was
made to just `note the concern' and proceed on. Sandra Loosemore and
JonL White concur with this position.