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

Issue EVAL-WHEN-NON-TOP-LEVEL, v5



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
-------