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

new, improved EVAL-WHEN proposal



Issue:		EVAL-WHEN-NON-TOP-LEVEL
References:	CLtL p. 69-70
		Issue DEFINING-MACROS-NON-TOP-LEVEL
Category:	CLARIFICATION, ENHANCEMENT
Edit History:   6-May-88, V1 by Sandra Loosemore


Problem Description:

The current description of how the compiler should handle EVAL-WHEN
only makes sense when it appears as a top-level form in the file being
compiled.  Other proposals being considered by the compiler cleanup
committee require that we clarify how the compiler should process
EVAL-WHENs appearing at non-top-level, such as within LET or FUNCTION
special forms. 


Proposal:  EVAL-WHEN-NON-TOP-LEVEL:CLARIFY

In this proposal, we view EVAL-WHEN as a macro which expands into a
PROGN containing the body of the EVAL-WHEN when the context in which it
is expanded matches one of the situations listed, or NIL otherwise.
However, it is explicitly *not* proposed that EVAL-WHEN's status as
a special form be changed.

The EVAL situation corresponds to the normal processing of the
interpreter.  If EVAL is specified, the interpreter evaluates each
form in the body of the EVAL-WHEN as an implicit PROGN, using the
current lexical environment.  If the EVAL situation is not specified,
then the interpreter must return NIL as the value of the EVAL-WHEN
form, without evaluating the body. 

The LOAD situation corresponds to the normal processing of the
compiler.  If LOAD is specified, the compiler treats the EVAL-WHEN as
a PROGN and compiles the body in the normal way in the appropriate
lexical environment.  Otherwise, the form is equivalent to specifying
a constant value of NIL.  (The name LOAD is something of a misnomer,
because ``evaluation'' of the body happens not at LOAD time, but when
the EVAL-WHEN form would normally be ``evaluated''.  For example, if
an EVAL-WHEN form appears in the body of a DEFUN, it is ``evaluated''
when that function is called.)

The COMPILE situation is a special case; it indicates that the
compiler itself should evaluate the body of the EVAL-WHEN form as an
implicit PROGN in the null lexical environment.  During the
evaluation, if a nested EVAL-WHEN appears in the body, the interpreter
follows its usual rule of checking only whether or not the EVAL
situation is specified to decide whether or not the body of the nested
EVAL-WHEN should be processed. 

If both the COMPILE and LOAD situations are specified, the compiler
first performs the evaluation for the COMPILE situation.  Then, the
normal processing for the LOAD situation takes place, except that the
compile-time evaluation of nested (EVAL-WHEN (COMPILE) ...) forms in
the body is suppressed, preventing repeated evaluations of subforms.

(EVAL-WHEN (COMPILE) ...) should be used with caution in non-top-level
situations.  For example, if the following appears as a top level form
in a file being compiled

    (let ((x  (some-hairy-computation)))
        (eval-when (eval compile load) (print x)))

the variable X will be treated as special during the compile-time
evaluation, and the value printed will be its symbol-value.  To
guarantee consistency between compile-time evaluation and the normal
processing, one should wrap the entire top-level form in an EVAL-WHEN,
as follows:

    (eval-when (eval-compile load)
        (let ((x  (some-hairy-computation)))
	    (print x)))


Rationale:

The behavior of top-level EVAL-WHENs as specified in this proposal
remains almost identical to that specified in CLtL.  The major
addition is specifying the lexical environment in which non-top-level
EVAL-WHENs are processed.  It is clear that the COMPILE situation must
always be processed in the null lexical environment, since the actual
lexical environment is not available at compile time.  Having the EVAL
and LOAD situations evaluate in the proper environment leads to
differing semantics, but it appears to be the behavior that most
people expect, and it is also the easiest to implement.

Suppression of COMPILE evaluations in nested EVAL-WHENs is necessary
to achieve certain desirable behaviors, such as the macro example in
section 4 of the DEFINING-MACROS-NON-TOP-LEVEL proposal.

Although viewing EVAL-WHEN as a macro is useful for purposes of 
explanation, user code is likely to want to continue to treat EVAL-WHEN
as a special form.  For example, a preprocessor that performs a code
walk should leave EVAL-WHENs intact, since the context in which the
preprocessor runs may not be the same as the context in which the code
it produces runs.


Current Practice:


Cost to implementors:

Probably fairly minor in most implementations.  

As an implementation technique, we suggest implementing EVAL-WHEN as a 
macro which uses a state variable to keep track of the current context.
A sample implementation might look something like:

    (defvar *compiling-p* nil "Are we compiling or interpreting?")
    
    (defmacro eval-when (situations &body body)
        (cond 
    
              ;; If the COMPILE situation applies, evaluate the body now
              ;;    and also see if we need to do the LOAD situation too.
              ;;    If so, shadow the definition of EVAL-WHEN to make it
              ;;    ignore nested compile-time evaluation requests, and
              ;;    return the body.
    
              ((and *compiling-p* (member 'compile situations))
               (eval `(progn ,@body))
               (if (member 'load situations)
                   `(macrolet ((eval-when (situations &body body)
                                 (if (member 'load situations)
                                     `(progn ,@body)
                                     'nil)))
                        ,@body)
                   'nil))
    
              ;; If either the EVAL or LOAD situation applies, return a PROGN.
    
              ((if *compiling-p*
                   (member 'load situations)
                   (member 'eval situations))
               `(progn ,@body))
    
              ;; Otherwise no situation applies, so just return NIL.
    
              (t
               'nil)))
    
    
    ;;; Eval must bind *compiling-p* to NIL.
              
    (defun eval (form)
        (let ((*compiling-p* nil))
            :
            ))
    
    
    ;;; Compile and Compile-file must bind *compiling-p* to a non-nil value.
    
    (defun compile (name &optional definition)
        (let ((*compiling-p* t))
            :
            ))
    
    (defun compile-file (input-pathname &key output-file)
        (let ((*compiling-p* t))
            :
            ))


Cost to users:

Since CLtL does not currently specify what the meaning of EVAL-WHEN
forms at non-top-level is, existing code which depends on their use is
already nonportable.  Preventing repeated evaluations of subforms when
EVAL-WHENs are nested is unlikely to cause any serious compatibility
problems, since the current model would already result in only a
single evaluation in the case when the code is processed
interpretively.

There are also some situations concerning nested top-level occurences
of EVAL-WHEN that CLtL does not completely specify, to which this
proposal does assign a specific interpretation.  For example, CLtL is
unclear on whether or not (FOO) would ever be called, while in the
current proposal it would definitely not be called:

    (eval-when (compile)
        (eval-when (compile)
	    (foo)))


Benefits:

Clarifying the meaning of EVAL-WHEN allows the behavior of defining
macros such as DEFMACRO to be specified in terms of EVAL-WHEN.  As a
side effect, it would then become meaningful for defining macros to
appear at other than top-level. 


Discussion:

This proposal reflects what appears to be the consensus of the
compiler cleanup committee on this issue.

-------