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

draft of alternate proposal for EVAL-WHEN-NON-TOP-LEVEL



Here is a draft of an alternate EVAL-WHEN proposal.  Basically, what
I have done is to fix the problem with the IGNORE-COMPILE proposal
 from version 4 that would have caused multiple evaluations in some
cases, and merge that with the new interpretation of the EVAL
situation from Kent's GENERALIZE-EVAL proposal from version 5.
Note that this proposal meets all of the same goals that were set out
in the version 5 writeup.

Since a number of people were getting hung up on the idea of EVAL-WHEN
not passing through top-level-ness, I have introduced another term,
"unshielded".  Avoiding confusion is the only reason why I have not
made "unshielded" and "top-level" be the same (although I personally
think the writeup would be simpler if they were).  Note that the
writeup for issue DEFINING-MACROS-NON-TOP-LEVEL would also have to be
changed to use "unshielded" instead of "top-level".

I am open to suggestions on this writeup.  If everybody else hates
this and thinks that the the general direction here is totally wrong,
I will shut up and go along with the GENERALIZE-EVAL proposal.  But I
personally would like to see something like this included in the
writeup we send out to X3J13 to vote on.



Proposal EVAL-WHEN-NON-TOP-LEVEL:GENERALIZE-EVAL-FIX-NESTING:

  Replace the description of EVAL-WHEN with the following:

  EVAL-WHEN ({situation}*) {form}*			[Special Form]
  
  The body of an EVAL-WHEN form is processed as an implicit PROGN, but
  only if the situations listed match the processing context.  Each
  SITUATION must be a symbol, either COMPILE, LOAD, or EVAL.
  
  The context in which an EVAL-WHEN form is processed is referred to as
  either shielded or unshielded.  The EVAL situation controls processing
  of the body in shielded contexts, and the COMPILE and LOAD situations
  together control processing of the body in unshielded contexts.
  
  Forms processed by COMPILE-FILE are considered to be shielded if
  they appear at non-top-level or are enclosed within an EVAL-WHEN.
  Forms that are processed by the COMPILE and EVAL functions are always
  considered to be non-top-level and therefore shielded.  
  
  The behavior of EVAL-WHEN may be more precisely understood in terms
  of a model of how the file compiler, COMPILE-FILE, processes forms
  in a file to be compiled.  Successive top-level forms are read from
  the file using READ and examined by the file compiler before reading
  the next.
  
  * If the form is a macro call, it is expanded and the result is 
    examined recursively as a top-level form.
  
  * If the form is a PROGN, each of its body forms are sequentially
    examined as top-level forms.
  
  * If the form is a COMPILER-LET, MACROLET, or SYMBOL-MACROLET, the file
    compiler makes the appropriate bindings and the body forms are
    recursively examined as an implicit top-level PROGN with those 
    bindings in effect.
  
  * If the form is an EVAL-WHEN form and the context is unshielded, then
    the file compiler performs two processing steps.  First, if the
    EVAL-WHEN form specifies the COMPILE situation, then the body of the
    EVAL-WHEN is evaluated as an implicit PROGN in the executing
    environment of the compiler.  Then, if the LOAD situation is
    specified, the body is recursively examined as an implicit top-level
    PROGN, but in a shielded context.
  
  * In all other cases, the compiler arranges for the form to be executed
    when the compiled file is loaded.  Any subforms are considered to be
    non-top-level forms.
  
  If an EVAL-WHEN form appears in a shielded context and the EVAL
  situation is specified, then its body is processed as an implicit
  PROGN.  If the EVAL situation is not specified, the body is ignored
  and the EVAL-WHEN form returns a value of NIL.

  
Clarifications/Consequences:

  The following effects are logical consequences of the above proposal:

   * It is never the case that the execution of a single EVAL-WHEN
     expression will execute the body code more than once.

   * The keyword `EVAL' is a misnomer because execution of
     the body need not be done by EVAL. In compiled code, such as
     (DEFUN FOO () (EVAL-WHEN (EVAL) (PRINT 'FOO)))
     it is permissible (even desirable) for the call to PRINT to be compiled.

   * Macros intended for use in top-level forms should arrange for all
     side-effects to be done by the forms in the macro expansion.
     The macro-expander itself should not do the side-effects.

   * Placing a variable binding around an EVAL-WHEN reliably captures the
     binding because non-top-level forms are always processed in a
     shielded context.

   * An outer EVAL-WHEN always shadows an inner EVAL-WHEN.  The behavior
     of nested EVAL-WHENs is symmetric for all three situations:

     An EVAL-WHEN in a shielded context that specifies the EVAL situation
     enables normal execution of its body.  If the EVAL situation is not
     specified, the normal execution of all body forms is prevented, even
     nested EVAL-WHENs that do specify the EVAL situation.
     
     An EVAL-WHEN in an unshielded context that specifies the LOAD
     situation enables load-time execution of its body.  If the LOAD
     situation is not specified, the load-time execution of all body forms
     is prevented, even nested EVAL-WHENs that do specify the LOAD
     situation.
     
     An EVAL-WHEN in an unshielded context that specifies the COMPILE
     situation enables compile-time execution of its body.  If the COMPILE
     situation is not specified, the compile-time execution of all body
     forms is prevented, even nested EVAL-WHENs that do specify the COMPILE
     situation.

   * One possible implementation of EVAL-WHEN is as a macro, assuming
     some implementation-dependent way of determining whether an environment
     is a top-level or non-top-level environment.

      (defmacro eval-when (situations &body body &environment env)
          (if (not (top-level-environment-p env))
              (expand-shielded-eval-when situations body)
              (progn
                  (if (member 'compile situations)
                      (mapcar #'eval body))
                  (if (member 'load situations)
                      `(macrolet ((eval-when (s &body b)
                                      (expand-shielded-eval-when s b)))
                           ,@body)
                      nil))
              ))
      
      (defun expand-shielded-eval-when (situations body)
          (if (member 'eval situations)
              `(progn ,@body)
              nil))

     

Test Cases:

  ;; #1: The EVAL-WHEN in this case is in a non-top-level position, so only
  ;;     the EVAL keyword is considered. At compile time, this has no effect.
  ;;     At normal execution time this sets (SYMBOL-FUNCTION 'FOO1) to a 
  ;;     function which returns 1.
 
  (LET ((X 1))
    (EVAL-WHEN (EVAL LOAD COMPILE)
      (SETF (SYMBOL-FUNCTION 'FOO1) #'(LAMBDA () X))))
 
  ;; #2: If this expression occurs at the top-level of a file to be compiled,
  ;;     it has BOTH a compile time AND a load-time effect of setting
  ;;     (SYMBOL-FUNCTION 'FOO2) to a function which returns 2.
 
  (EVAL-WHEN (EVAL LOAD COMPILE)
    (LET ((X 2))
      (EVAL-WHEN (EVAL LOAD COMPILE)
        (SETF (SYMBOL-FUNCTION 'FOO2) #'(LAMBDA () X)))))
 
  ;; #3: If this expression occurs at the top-level of a file to be compiled,
  ;;     it has BOTH a compile time AND a load-time effect of setting the
  ;;     function cell of FOO3 to a function which returns 3.
 
  (EVAL-WHEN (EVAL LOAD COMPILE)
    (SETF (SYMBOL-FUNCTION 'FOO3) #'(LAMBDA () 3)))
 
  ;; #4: This always does nothing. It simply returns NIL.
 
  (EVAL-WHEN (COMPILE)
    (EVAL-WHEN (COMPILE) 
      (PRINT 'FOO4)))
 
  ;; #5: If this form occurs at top-level of a file to be compiled, FOO5 is
  ;;     printed at compile time. If this form occurs in a non-top-level
  ;;     position, nothing is printed at compile time. Regardless of context,
  ;;     nothing is ever printed at load time or execution time.
 
  (EVAL-WHEN (COMPILE) 
    (EVAL-WHEN (EVAL)
      (PRINT 'FOO5)))

  ;; #6: The description of EVAL-WHEN in CLtL says that this form will print
  ;;	 FOO6 at compile time.  The description of EVAL-WHEN in this proposal
  ;;     says it will never print anything.

  (EVAL-WHEN (EVAL LOAD)
    (EVAL-WHEN (COMPILE)
      (PRINT 'FOO6)))

 
Rationale:
  
  This behavior specified by this proposal is simple and easy to 
  understand, and extends the behavior of EVAL-WHEN usefully to 
  non-top-level situations.  It is largely compatible with the behavior
  of EVAL-WHEN specified in CLtL, except for the situation shown in
  example #6 above.  Some people find the nesting behavior of EVAL-WHEN
  specified in this proposal to be an improvement over that specified
  in CLtL.

  This gives a useful meaning to EVAL-WHEN that supports useful and
  predictable behavior if defining macros are used in a non-top-level
  situation.
-------