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

Eval-When -- a radical view

You sure put "a mouthful" in your reply!  I'm not sure that I'll be able now
to comment on all the things you've brought up, but here goes ...

re: A narrow complaint that I have is that it seems you haven't been as 
    consistent as you could be in extending the semantics described in CLTL.  
    In your version, compiling:
    (eval-when (compile)
      (eval-when (load)
	(print 'foo)))
    . . . 

You're quite right; the code I suggested for EVAL-WHEN should have the line:
	(:eval-before-compile  T)
changed to be
	(:eval-before-compile  (or (memq 'eval situations)
                                   (memq 'load situations)))
That should better reflect the semantics of what CLtL calls "compile-time-too".

re: It should also be clear that using a dynamic variable *EVAL-WHEN-STATE* is
    an implementation detail, and not part of the inherent semantics.  For one,
    requiring EVAL to bind a special prevents it from being tail-recursive.  
    What you really want to do is lexically modify the evaluation rules.  Using
    a dynamic variable bound by EVAL and COMPILE is an approximation of this, 
    but things become more confusing when there are any other ways for the 
    lexical environment to become null.  

Right again.  Mostly my use of the dynamic variable *EVAL-WHEN-STATE* is 
merely to make it easier to understand a rather simple defmacro definition 

re: Even supposing we could clearly define EVAL-WHEN in a way that extended 
    cleanly across all implementations that we want to support, EVAL-WHEN 
    would still be bad because it is too powerful: it allows users to say 
    things that aren't meaningful across all implementations -- it encourages 
    writing of programs that don't work interpreted (or compiled).

This paragraph is representative of large section of your note, basically
saying that trying to "patch up" EVAL-WHEN is a doomed process.  I disagree
mildly.  In particular, the ability to "say things that aren't meaningful"
is not focused enough to warrant tossing out eval-when.  I agree that the
willy-nilly generation of legal syntatic forms will come up with some
meaningless phrases; why, even my proposal presented an interpretation for

   (defun foo (x) 
     (mumble (eval-when (load) (bletch x))))

even though that is clearly a meaningless thing to do. [my presentation was
to view the 'load' situation as meaning that this should be incorporated
into the compiled code and only into the compiled code; Sandra's presentation
 -- at least verbally at the Friday morning meetings -- was that it could
mean something like #,  and in a way I prefer hers now]  Rather, I like the 
simplicity of EVAL-WHEN for the combinations that do make good sense.  As
you say, 

    I searched through our system sources looking for all uses of EVAL-WHEN. 
    Nearly all uses involved one of two situations lists:
    . . . 
    There were also quite a few uses of the (COMPILE) situation list, but 
    nearly all of these were erroneous; the code wouldn't have worked 

On the other hand, in our system, the relatively few instances of the (COMPILE)
situation are precisely for code that can't work interpreted.  I myself have
even inserted a couple of lines like
    (eval-when (eval) 
      (error "You Loser! this file can't be run interpretively"))
We have a few places in our system that are defined only by the code-generator
parts of the compiler; some of these functions have interpreter entries that
look like the classic:
    (defun car (x)
      (check-type x list)
      (car x))
but most are merely internal compiler stuff that doesn't need such an entry.
Files that reference such functions simply can't be run except when compiled.

But the whole point is that whether the number of "meaningful" situations
is 3 or 4 or 5 out of a possible 8, that is no reason to give up just
because the other few are not particularly useful.

Now, Sandra brings up another good point (which you too, Rob, mention) --

    The problem of having the same piece of code sometimes get evaluated in
    the current lexical environment and sometimes not is going to be easy to
    resolve.  There does not seem to be much disagreement that the COMPILE
    situation must do its evaluation in the null lexical environment. . . . 

Maybe I'll exercise my perogative to bring in some "disagreement".  Perhaps 
feeding an &environment argument to eval-when isn't so bad after all.  In 
terms of my trial-baloon defmacro for eval-when, the only problematical point
is how to transmit an outter environment down through the code:
        (mapc #'eval-internal code)
I'm sure that some suitable solution could be found.  Maybe even binding
the *EVAL-WHEN-STATE* to it instead of :EVAL-BEFORE-COMPILE could work?
Also Sandra's mention of a possible definition of DEFMACRO in terms of
EVAL-WHEN and SETF(MACRO-FUNCTION ...) adds weight to the need for a solution.
At any rate, I think this is indeed an important enough problem to spend
some time thinking about [looking for a consistent solution that is, not
just looking for reasons to chuck out the whole kit-and-kaboodle].

If we were to adopt some notion that each "file" is a lexical entity --  
equivalent for scoping as if there were a giant (locally ....) enclosing the 
entire file -- then it would be even more important to pass in &environment
information to eval-when.  [But of course you couldn't do this "enclosing"
literally since that would mean reading in all the forms of the file before 
processing any of them].  My proposal for a lexical-file-proclaim needs
such a notion of lexical scoping, and I believe it accurately reflects
desiderata set out by Beckerele and others about the semantics of what
a "file" is such that you can apply COMPILE-FILE to it; but this is a
topic for yet another time.

Incidentally, the example that Sandra offers

    (eval-when (eval compile load)
	(let ((x  (compute-some-value)))
	    (defmacro foo (y)
		`(+ ,x ,y))))

Could just as well have been

    (eval-when (eval compile)
	(let ((x  (compute-some-value)))
	    (defmacro foo (y)
		`(+ ,x ,y))))

That is, the proponents of this style frequently need it only in the
compiler's environment.  For an implementation that doesn't have an
interpreter, then the situation marker (eval compile) is still right,
since it means first compiling
	(let ((x  (compute-some-value)))
	    (defmacro foo (y)
		`(+ ,x ,y)))
and then immediately afterwards calling that compiled "thunk" to be executed.
The macro is now "alive" for subsequent compilations of forms found later in
the file.

-- JonL --