In Common Lisp, when do you need to use eval-when, and how do you know?
Asked Answered
C

1

15

A required use of eval-when is to ensure that functions which a macro depends on are available at the time the macro is compiled and is used. However, I can't think of an example that would demonstrate the consequences of not using eval-when.

(defpackage :eval-when
  (:use :cl))

(in-package :eval-when)

(defun util-fun (x) (* x x))

(defmacro needs-help (x) `(let ((a (util-fun ,x))) a))

;; use it in the same file

(defun use-the-macro (x) (needs-help x))

(use-the-macro 5)

If I understand correctly, the (defun util-fun ...) should be wrapped with eval-when.

EDIT: As you'll see from the Answer, there's a problem with this example: it doesn't actually call UTIL-FUN at compile time. This explains why no error is given, because it's not an error. But the question is still valid in that it highlights a new user's confusion.

However, from the REPL, no error or warning is issued during compilation, load or usage (SBCL 1.3.20):

; SLIME 2.19
CL-USER> (uiop:getcwd)
#P"/home/anticrisis/dev/common-lisp/eval-when/"
CL-USER> (compile-file "eval-when.lisp")
; compiling file "/home/anticrisis/dev/common-lisp/eval-when/eval-when.lisp" (written 14 AUG 2017 11:30:49 AM):
; compiling (DEFPACKAGE :EVAL-WHEN ...)
; compiling (IN-PACKAGE :EVAL-WHEN)
; compiling (DEFUN UTIL-FUN ...)
; compiling (DEFMACRO NEEDS-HELP ...)
; compiling (DEFUN USE-THE-MACRO ...)
; compiling (USE-THE-MACRO 5)

; /home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl written
; compilation finished in 0:00:00.009
#P"/home/anticrisis/dev/common-lisp/eval-when/eval-when.fasl"
NIL
NIL
CL-USER> (in-package :eval-when)
#<PACKAGE "EVAL-WHEN">
EVAL-WHEN> (use-the-macro 3)
; Evaluation aborted on #<UNDEFINED-FUNCTION USE-THE-MACRO {10035E1103}>.
EVAL-WHEN> (needs-help 4)
; Evaluation aborted on #<UNDEFINED-FUNCTION UTIL-FUN {100387FE33}>.
EVAL-WHEN> (load "eval-when.lisp")
T
EVAL-WHEN> (use-the-macro 3)
9
EVAL-WHEN> (needs-help 4)
16
EVAL-WHEN> 

Note that normally I use C-c C-k to eval and load a file to the repl, but here, I'm using the compile-file and load commands to demonstrate that no error occurs. (I do receive an error when I try to use the functions after they're compiled but before they are loaded, but that would occur with any unloaded code.)

There are prior questions and comments that relate to this:

  • This previous StackOverflow answer seems to very plainly say that any function which is used by a macro must be enclosed by the eval-when form, or loaded in a separate file.

  • This comment from coredump is also very clear:

    When the macro is expanded, any function that the macro calls must be defined. If you have a compilation unit which defines a macro, which calls functions, but you don't actually use the macro in the same compilation unit, you don't need eval-when. If however, you define an aux. function, a macro and want to use your macro right off after you define it, then the implementation might complain that the aux. function is unknown – coredump

Given that, why does my example not generate an error? Will my example fail under other scenarios? An example of the compile-time, load-time, or run-time error generated when failing to properly use eval-when would be helpful to my understanding.

Thank you for your patience!

Cane answered 14/8, 2017 at 0:8 Comment(9)
You might want to check out this question https://mcmap.net/q/768100/-eval-when-uses. You can also read the eval-when section in PCL gigamonkeys.com/book/the-special-operators.htmlSweyn
@DavidHodge Thank you for the referral; I've expanded my question to be more specific.Cane
The other answer you referred to talks about compiling a file. You mention a REPL. Those are two different things. Please also post a specific question, best with code.Bowfin
When the macro is expanded, any function that the macro calls must be defined. If you have a compilation unit which defines a macro, which calls functions, but you don't actually use the macro in the same compilation unit, you don't need eval-when. If however, you define an aux. function, a macro and want to use your macro right off after you define it, then the implementation might complain that the aux. function is unknownLandgrave
@Landgrave Aha! Now that makes sense. In order to record the answer for posterity, I'll try to follow Rainer's suggestion and post a better question.Cane
@RainerJoswig Thank you, I've rewritten the question and added example code. I appreciate your patience.Cane
Your code does not use the function at compile time. It just expands into it. There is no need for EVAl-WHEN.Bowfin
@RainerJoswig I just noticed that :-( However, if I change the code to actually use util-fun, I get an "unused variable" warning, which is a good clue that I've done something wrong, but doesn't suggest that it's an eval-when failureCane
@RainerJoswig Ok, now I've got it, and see compiler error "The function EVAL-WHEN::UTIL-FUN is undefined." I wonder if there's a way for me to salvage this question in case it's useful to others on the same macro-confused track I was on.Cane
B
28

Remember

EVAL-WHEN is there to tell the file compiler whether it should execute code at compile-time (which it usually does not do for example for function definitions) and whether it should arrange the compiled code in the compiled file to be executed at load time. This only works for top-level forms.

Common Lisp runs the file compiler (remember we are talking about compiling files, not executing in a REPL) in a full Lisp environment and can run arbitrary code at compile time (for example as part of the development environment's tools, to generate code, to optimize code, etc.). If the file compiler wants to run code, then the definitions need to be known to the file compiler.

Also remember, that during macro expansion the code of the macro gets executed to generate the expanded code. All the functions and macros that the macro itself calls to compute the code, need to be available at compile time. What does not need to be available at compile time, is the code the macro form expands to.

This is sometimes a source of confusion, but it can be learned and then using it isn't too hard. But the confusing part here is that the file compiler itself is programmable and can run Lisp code at compile time. Thus we need to understand the concept that code might be running at different situations: in a REPL, at load time, at compile time, during macro expansion, at runtime, etc.

Also remember that when you compile a file, you need to load the file then, if the compiler needs to call parts of it later. If a function is just compiled, the file compiler will not store the code in the compile-time environment and also not after finishing the compilation of the file. If you need the code to be executed, then you need to load the compiled code -> or use EVAL-WHEN -> see below.

Your code

Your code does not call the function util-fun at compile time. So the function does not need to be available in the compile-time environment.

An example

Another example, where the function is actually called, see below. This is code in a Lisp file, to be compiled by compile-file.

(defun run-at-compile-time ()
  (print 'I-am-called-at-compile-time))

(defmacro foo ()
  (run-at-compile-time)             ; this function is called for its
                                    ;   side-effect: it prints something
  '(print 'I-am-called-at-runtime)) ; this code is returned

(foo)       ; we use the macro in our code, the compiler needs to expand it.
            ; Thus during macro expansion the function
            ; RUN-AT-COMPILE-TIME will be called.

So during macro expansion the macro foo likes to call the function run-at-compile-time, which is defined in the same file. Since it is not available in the compile time environment, this is an error. The file compiler only generates the code for the function to be stored on disk, such that when the compiled file is loaded, then the function gets defined. But it does not define the function inside the Lisp running the compiler -> the file compiler can't call it.

Introducing EVAL-WHEN

To tell the compiler to also let the compile time environment know about it, you need to wrap it in an EVAL-WHEN and add the :compile-toplevel situation. Then when the file compiler sees the function at toplevel, it runs the defining macro.

(eval-when 

    (:compile-toplevel  ; this top-level form will be executed by the
                        ;  file compiler

     :load-toplevel     ; this top-level form will be executed at load-time
                        ;  of the compiled file

     :execute           ; executed whenever else                           
                        ;  top-level and non-top-level
                        ;  EVAL, COMPILE, ...

    )

    (defun run-at-compile-time ()
      (print 'I-am-called-at-compile-time))

 )

You can also mention just one or two of the situations. For example the form could be executed when the file compiler sees it at top-level and only then. It would not be executed at load time or other situations.

Bowfin answered 14/8, 2017 at 22:7 Comment(4)
Superb explanation, thank you, but there's one other point I wonder if you could clarify: the fact that in practice, if your macro definitions are in a file separate from the files which actually use them, then this problem is never visible, and eval-when is not necessary. This, I believe, contributed to my own confusion.Cane
@anticrisis: then you have to make sure that the files, the code (source or compiled) depends on, are LOADED (!) into the compiling Lisp before compiling the depending files. Just compiling them is not enough.Bowfin
What's the difference between "load time" (ie, :load-toplevel) and "whenever else" (:execute)? From the REPL they're the same thing.Preparatory
:load-toplevel influences only the file compilation of top-level forms, :execute has always influence (any form, also during evaluation of source, loading of source or using COMPILE).Bowfin

© 2022 - 2024 — McMap. All rights reserved.