Search code examples
common-lispcompile-timeeval-when

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


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!


Solution

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