[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Issue EVAL-WHEN-NON-TOP-LEVEL, v5
- To: cl-compiler@SAIL.STANFORD.EDU
- Subject: Issue EVAL-WHEN-NON-TOP-LEVEL, v5
- From: Kim A. Barrett <IIM%ECLA@ECLC.USC.EDU>
- Date: Sun 19 Feb 89 15:50:40-PST
- Cc: iim%ECLA@ECLC.USC.EDU
I think a lot of my disagreement with this proposal is founded on differing
ideas about what the various situations mean.
Under version 4 model:
EVAL the interpreter (EVAL) should process the body.
LOAD the compiler should process the body.
COMPILE the compiler should make the interpreter process the body at
compile-time.
Under version 5 model:
EVAL says I want code run in embedded code.
LOAD says I want code run at top-level when loading.
COMPILE says I want code run at top-level when compiling.
The COMPILE situations are the same. The differences are in the LOAD and EVAL
situations, and possibly in how they interact with the COMPILE situation.
For nesting behavior, an implication of version 4 is that an EVAL-WHEN always
limits the set of situations which will apply in EVAL-WHEN forms which are
nested within it. This is not true of version 5.
Here are a couple of concrete examples where these models differ.
1. DEFMACRO
If we write DEFMACRO as follows (proponents of both version 4 and version 5
believe this is a reasonable way to write it):
(defmacro DEFMACRO (name bvl &body body)
`(progn
(eval-when (compile)
(compiler::note-defmacro ',name ',bvl ',body))
(setf (macro-function ',name) #'(lambda ...))))
If we wrap a defmacro in an (eval-when (compile load eval) ...) then we get the
following behaviors when compiled (both versions exhibit the same behavior when
interpreted).
effective expansion
(eval-when (compile load eval)
(eval-when (compile)
(compiler::note-defmacro ',name ',bvl ',body))
(setf (macro-function ',name) #'(lambda ...)))
version 4
Step 1. The outer EVAL-WHEN specifies the COMPILE situation and we are at
top-level, so call EVAL on each of the body forms. The first body form is
an EVAL-WHEN without the EVAL situation, so it evaluates to NIL. The
second body form is evaluated.
Step 2. The outer EVAL-WHEN specifies the LOAD situation, so process the
body as a non-top-level PROGN. The first body form is an EVAL-WHEN
specifying only the COMPILE situation. Since we are not at top-level, it
is treated as NIL. The second form is processed normally.
version 5
The outer EVAL-WHEN specifies the COMPILE situation, so switch to
compile-time-too mode while processing the body forms. The first body form
is an EVAL-WHEN with only the COMPILE situation specified, so evaluate the
body as an implicit PROGN. The second form is not an EVAL-WHEN. Since we
are in compile-time-too mode, evaluate it. Now do normal processing on it.
The difference is that version 4 ignores the inner (EVAL-WHEN (COMPILE) ...),
while version 5 evaluates it. It seems rather pointless to record the macro in
the compiler's remote environment when you are about to define it in the local
environment. Two counter-arguments come to mind. First, you may be redefining
a macro that appeared earlier in the file without having such an EVAL-WHEN
wrapped around it. But most of the comments I've heard on these mailing lists
about similar things is that they are an error. Second, this may be necessary
(and possibly the only way) to define a recursive macro (that is, a macro whose
expander function (not its expansion) calls that same macro). Do we really
need to support such critters?
2. The EVAL situation
Some implications of version 5
(EVAL-WHEN (EVAL) . BODY) == NIL when at top-level
(EVAL-WHEN (EVAL) . BODY) == (PROGN . BODY) when at non-top-level
Thus, under version 5, there is no way (at non-top-level) to distinguish
between the case where the compiler is processing the code and the case where
the interpreter is processing the code, and the behavior of the EVAL situation
is radically different, depending on whether you are at top-level or not, which
makes it harder to reason about what the EVAL situation means, and whether or
when you should use it.
Under version 4, the compiler and interpreter cases are distinguishable. An
example where this distinction was useful is a definition of
PCL::LOAD-TIME-EVAL I wrote some time ago for our system:
(defmacro load-time-eval (form)
(let ((gensym (gensym)))
`(block ,gensym
(eval-when (eval)
(return-from ,gensym (eval ',form)))
(eval-when (load)
<< stuff to hook into compiler's load-time evaluation stuff >>))))
I think that we are approaching agreement on this stuff. I feel comfortable
with most of the points made in the Backgroud/Analysis and
Clarifications/Consequences sections of version 5. I don't have a strong
objection to introducing a compile-time-too state into version 4 to keep the
evaluation due to a COMPILE situation in lock-step with the normal processing
of the forms. My main reason for wanting to avoid it is that the resulting
description seems (to me, at least) much simpler and easier to understand.
kab
-------