[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
LOAD-TIME-EVAL
- To: cl-compiler@sail.stanford.edu
- Subject: LOAD-TIME-EVAL
- From: sandra%defun@cs.utah.edu (Sandra J Loosemore)
- Date: Mon, 1 Aug 88 13:28:56 MDT
Here is the writeup from the cleanup committee on the LOAD-TIME-EVAL
issue. At this point, I'm leaning towards some variant of the
NEW-SPECIAL-FORM proposal, but I'd like to hear comments from the
rest of you before I do any real "work" on this.
-Sandra
Issue: LOAD-TIME-EVAL
References: #, (p. 356), (EVAL-WHEN (LOAD) ...) (p. 69-70)
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)
Status: For internal discussion
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.
Also, CLtL is vague about whether the result of this early evaluation
is re-evaluated at runtime. The meaning of #,exp in an for-evaluation
position is unclear. Although CLtL doesn't come out and say so explicitly,
portable code must currently use only '#,exp to get consistent behavior
across implementations.
CLtL is also vague on whether the result of a #, expression may be
treated as a read-only constant by the loader (and hence shared with
other programs). Users probably want some both read-only and modifiable
load-time constants, so this may not be simply an issue of deciding on
a single "right answer".
Proposal (LOAD-TIME-EVAL:QUOTED-MAGIC-TOKEN):
Add a function MAKE-LOAD-TIME-CONSTANT, as described here:
MAKE-LOAD-TIME-CONSTANT form env &optional read-only-p [Function]
FORM is a Lisp form. ENV is an environment of the sort received
by the &ENVIRONMENT argument to a macro.
When MAKE-LOAD-TIME-CONSTANT is called from the interpreter or the
COMPILE function, it simply evaluates FORM in the null lexical
environment and returns its value. When MAKE-LOAD-TIME-CONSTANT is
called during a file compilation, the result is a special object
that is recognized at load time, when it occurs inside a constant.
At load time, FORM is evaluated and its value is substituted for
the object.
MAKE-LOAD-TIME-CONSTANT uses its ENV argument and/or dynamic state
to determine whether it is being called during a file compilation.
Until Common Lisp is modified to specify the semantics of file
compilation more precisely, this is necessarily implementation
dependent.
The READ-ONLY-P argument designates whether the result can be considered
read-only constant. If NIL, 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.
Specify that '(... #,exp ...) is equivalent to
#.`(... ,(MAKE-LOAD-TIME-CONSTANT exp NIL T) ...).
Rationale:
This approach is the most compatible with existing practice.
Cost to Implementors:
The cost to implementors will depend on how #, is implemented.
In some implementations, the primitives for implementing
MAKE-LOAD-TIME-CONSTANT may already exist, in others, more substantial
changes may be required.
Cost to Users:
This change is upward compatible with user code.
Benefits:
It would be possible for macros to expand into load time constants.
Examples:
Case QUOTED-MAGIC-TOKEN-1:
(defmacro print-software-version (&environment env)
`(quote ,(make-load-time-constant
'(format T "~A~%" (software-version))
env)))
When interpreted or processed during invocation of COMPILE, this
macro prints the value of (software-version) at macro expansion
time and expands into (quote nil). When macroexpanded during a
file compilation, printing is deferred until the compiled file is
loaded, and the constant is still (quote nil).
Case QUOTED-MAGIC-TOKEN-2:
(defmacro table-of-tables (&rest predicates &environment env)
`(quote ,(mapcar #'(lambda (predicate)
`(,predicate
,(make-load-time-constant
`(make-hash-table :test ',predicate)
env)))
predicates)))
(table-of-tables eql equal) expands into
(quote ((eql #<table :test eql>) (equal #<table :test equal>)))
except that when macroexpanded during a file compilation,
the tables are not created until load time. This example
shows that the <object> returned by make-load-time-constant is
recognized even when it is interior to a quoted constant.
Proposal (LOAD-TIME-EVAL:NEW-SPECIAL-FORM):
Add a new special form, LOAD-TIME-CONSTANT, which has the following
contract:
LOAD-TIME-CONSTANT form &optional read-only-p [Special Form]
All processing of the FORM is deferred until the expression is
in the "runtime" environment. Once that environment is available,
FORM is evaluated in the null lexical environment and the result
is both returned and saved for immediate access by the program
on subsequent evaluations.
In the interpreter, the FORM may be evaluated during pre-processing
(if any) or dynamically when the LOAD-TIME-CONSTANT is first seen
(in a non-pre-processing implementation). If the same LOAD-TIME-CONSTANT
expression is later seen again by the interpreter, the previously
obtained result is immediately retrieved and returned as the result
of evaluating the object; no re-evaluation occurs.
If the LOAD-TIME-CONSTANT expression is seen by the file compiler
(eg, COMPILE-FILE), the compiler arranges for all semantic processing
of FORM (including macro expansion) to occur at load time in a null
lexical environment (independent of whether any value has been cached
for interpreter use). At runtime, the result of that evaluation will
be treated as an immediate quantity; no re-evaluation occurs.
If a LOAD-TIME-CONSTANT expression is seen by the runtime compiler
(eg, COMPILE), the compiler checks for a cached value which may have
been produced by the interpreter. If one is found, it is used. If no
such value is found, the runtime compiler will evaluate the FORM in
a null lexical environment and use that value. The value used will be
treated as an immediate quantity in the code which is produced; no
re-evaluation occurs.
Note that since some implementations are compiled-only (that is, they
implement their interpreter using a compiler pre-pass) and some are
interpreted-only (that is, they implement their compiler as a null
operation and use only an interpreter), the question of whether the
interpreter or the compiler will end up doing the processing is left
somewhat vague. The programmer may assume only that the given FORM
will be evaluated only once for each time it is loaded into a runtime
environment.
Note, however, that in the case of quoted code (processed by explicit
use of EVAL), each call to EVAL is treated like a load. Caching may not
be implemented by having LOAD-TIME-CONSTANT displace its source level
call. So while
(DEFVAR *FOO* 1)
(DEFUN FOO () (LOAD-TIME-CONSTANT (INCF *FOO*)))
will increment *FOO* only once,
(DEFUN FOO () (EVAL '(LOAD-TIME-CONSTANT (INCF *FOO*))))
will increment *FOO* once each time FOO is called.
The READ-ONLY-P argument designates whether the result can be considered
read-only constant. If NIL, 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.)
Make #,exp be equivalent to (LOAD-TIME-CONSTANT exp T). As such, it
would -always- appear in a for-evaluation position, and never inside quoted
structure.
Rationale:
By making the description of LOAD-TIME-CONSTANT defined as a special
form, we eliminate the need for it to take an environment argument.
By making #, agree with LOAD-TIME-CONSTANT in terms of where it may be
used, we simplify the description of the resulting language.
As discussed in more detail elsewhere in this proposal, the #, syntax
is currently only reliably useful -inside- quoted structure, but this
is unnecessarily complicated for most known uses. Since this proposal
suggests a meaning for #, only -outside- quoted structure, it is an
incompatible change (though not one that would necessarily require
vendors to immediately remove support for existing code).
Cost to Implementors:
This is an incompatible change to the program interface.
The cost is not trivial, but is not particularly high.
Most of the "hard" substrate required to support this proposal
probably already exist; in most cases, what needs to change is
only the way in which the substrate is presented to the
programmer.
Some code-walkers would have to be taught about this new
special form. Such changes would likely be trivial.
Cost to Users:
Very few users probably use #, right now. A very small amount of code
might need to be rewritten (and recompiled).
The following kinds rewrites exemplify the total amount of work needed
in the few situations that use #, currently:
'#,exp => #,exp
'(... #,exp ...) => #,`(... ,exp ...)
`(... #,exp ...) => `(... ,#,exp ...)
Some user-defined code-walkers would have to be taught about
this new special form. Such changes would also be likely be trivial.
Although change to #, is an incompatible one, vendors would be free to
provide compatibility support for the old behavior for whatever period
they deemed appropriate. #, is equivalent to, but not the same as,
(LOAD-TIME-CONSTANT exp T), so it might in fact expand into
(SYSTEM::LOAD-TIME-CONSTANT exp T) which might be both a macro that
expanded into (LOAD-TIME-CONSTANT exp T). SYSTEM::LOAD-TIME-CONSTANT
might be a magic token which was treated specially within quoted
structure as an unportable extension during a transition period.
Benefits:
Relatively consistent interpreter/compiler treatment of this special form
would be possible.
Pretty printing expressions in which LOAD-TIME-CONSTANT occurred would be
possible.
An expression such as `(... #,(... , ...) ...) would be meaningful.
Manipulating expressions involving uses of LOAD-TIME-CONSTANT would be
possible. Currently, calling READ is enough to trigger resolution of the
constant, so a program doing file-to-file source code rewrites must
either use a special readtable or resign itself to premature resolution
of load time constants.
Expressions involving quoted LOAD-TIME-CONSTANT expressions would be
possible to quote. Currently, the #, feature is syntactic, not semantic,
and so is not sensitive to levels of quotation. You can refer to a
load time constant by writing '#,exp but you cannot refer in turn to that
expression which refers to a load time constant by writing ''#,exp .
Under this NEW-SPECIAL-FORM proposal #,exp and '#,exp and ''#,exp (and
so on) are all usefully distinct.
It would be possible to use , in a #, expression. For example, the
expression:
Examples:
Case NEW-SPECIAL-FORM-1:
(defmacro print-load-timestamp ()
`(print (load-time-constant
`(load-timestamp ,(incf *foo*) ,(get-universal-time))
t)))
(defvar *foo* 0)
(defun test-1 () (print-load-timestamp))
(test-1)
CLtL does not define this situation.
Under this proposal, this code would print
(LOAD-TIMESTAMP 1 <<a-universal-time>>)
at the time the test case is loaded, whether interpreted or compiled.
Subsequent calls to (TEST-1) should print the identical expression.
Currently, no known implementation supports the proposed behavior.
Case NEW-SPECIAL-FORM-2:
(defun test-2 () (print #,'(+ 3 4)))
CLtL does not adequately define this situation.
Under this proposal, this would print (+ 3 4), whether interpreted
or compiled.
Currently, some compilers complain about the syntax, some arrange for
it to print (+ 3 4), and some arrange forit to print the result of
(+ 3 4), or 7.
Case NEW-SPECIAL-FORM-3:
(defun test-3 () (print '#,'(+ 3 4)))
Under CLtL, this would print (+ 3 4).
Under this proposal, the behavior would be undefined.
Currently, most implementations support the proposed behavior.
Case NEW-SPECIAL-FORM-4:
(pprint '(+ x #,(sqrt x)))
prints something re-readable like
(+ X #,(SQRT 2)) or (+ X (LOAD-TIME-CONSTANT (SQRT 2) T))
but not something like
(+ X 1.4142135)
Currently, no implementation is known to already support the
proposed behavior, but in principle it is possible for a valid
implementation to already do so.
Case NEW-SPECIAL-FORM-5:
(defmacro foo (x y)
`(member ,x #,`(foo ,,y baz)))
(macroexpand '(foo *bar*)) => ??
Under CLtL, this situation is not adequately defined.
Under this proposal, the macroexpansion would be
(member foo #,`(foo ,*bar* baz))
Currently, this triggers a read error such as "comma not in backquote"
or "SYSTEM::BACKQUOTE-COMMA undefined function" in most
implementations.
Current practice:
Although most implementations provide a substrate which would allow
program-mediated access to load time evaluation in some way, the language
only defines access to this substrate through the sharpsign read syntax.
Costs of Non-Adoption:
There are numerous possible uses for this facility. Among them are:
* Version control and system building facilities.
* The Common Lisp Object System.
* Language translators which desire to emulate "linking".
While every implementation of Common Lisp could certainly provide an
implementation specific facility capable of supporting such facilities,
portability of such facilities would suffer.
Benefits:
Portability and extended language power. The nature of each proposed
extension is such as to enable other extensions to be added more
gracefully. The Common Lisp Object System is a clear example.
Aesthetics:
These proposals fill a hole in the spectrum of alternatives for
deferring evaluation until a compiled file is loaded. Currently, code
which is read by the reader can arrange for it to be done, as can
top level code, but embedded code cannot. As such, these proposals
clarify and regularize existing parts of the language. Also, by
adding program-accessible entry points to facilities already provided
in a more contrived way, it makes the language easier to use.
Discussion:
There is likely to be some controversy about this proposal, since
there is no universally agreed upon formal processing model for
Common Lisp.
The cleanup committee seems to generally approve of the idea of a
load-time-eval capability, but a number of the details seem to need
ironing out.
Moon supported a previous draft of QUOTED-MAGIC-TOKEN. In this draft,
KMP changed the presentation and also added the READ-ONLY-P argument
in order to make it sit nicely with the alternate proposal,
NEW-SPECIAL-FORM. It's more than slightly possible that after all this
editing, Moon will have some problems with this version and want to
submit a refined draft.
Pitman supports NEW-SPECIAL-FORM.
Rees has expressed strong support for the idea of implementing #, as a
new special form rather than perpetuating the current state of affairs.
He had some input into the high-level content of this proposal, though
he hasn't reviewed any drafts. This paragraph is intended primarily to
incite him to say something definitive one way or the other.
----- End Forwarded Messages -----
**** End of Forwarded Message ****
-------