Search code examples
compilationmacroscommon-lisp

Macro with &key and &body parameters requires specifying key parameter


I have the following macro:

(defparameter *current-state* 'foo)

(defmacro when-state ((state &key (test #'eq)) &body body)
  `(when (funcall ,test *current-state* ,state)
     ,@body))

Using it in the REPL it works fine with or without specifying key parameter.

However, when using it in a top-level defun or lambda variable declaration, like so:

(defun foo ()
  (when-state ('foo)
    (format t "Foo")))

Then the file won't compile with SBCL with the following error:

; in: DEFUN FOO
;     (SENTO.FSM:WHEN-STATE ('SENTO.FSM::FOO)
;       (FORMAT T "Foo"))
; --> IF FUNCALL 
; ==>
;   1
; 
; caught ERROR:
;   Objects of type COMPILED-FUNCTION can't be dumped into fasl files.
; 
; note: The first argument never returns a value.

Only when explicitly specifying the key parameter it will compile fine.

What is the problem here?


Solution

  • See the difference:

    CL-USER 68 > (macroexpand-1 '(when-state ('foo)
                                   (format t "Foo")))
    (WHEN (FUNCALL #<Function EQ 80E003BDE9>
                   *CURRENT-STATE*
                   (QUOTE FOO))
      (FORMAT T "Foo"))
    T
    

    In above generated source code, the function EQ is included as an object. This can't be dumped.

    Now specify the test in the source:

    CL-USER 69 > (macroexpand-1 '(when-state ('foo :test #'eq)
                                   (format t "Foo")))
    (WHEN (FUNCALL (FUNCTION EQ)
                   *CURRENT-STATE*
                   (QUOTE FOO))
      (FORMAT T "Foo"))
    T
    

    Now quote the test in the WHEN-STATE definition:

    CL-USER 70 > (defmacro when-state ((state &key (test '#'eq)) &body body)
                   `(when (funcall ,test *current-state* ,state)
                      ,@body))
    WHEN-STATE
    
    CL-USER 71 > (macroexpand-1 '(when-state ('foo)
                                   (format t "Foo")))
    (WHEN (FUNCALL (FUNCTION EQ)
                   *CURRENT-STATE*
                   (QUOTE FOO))
      (FORMAT T "Foo"))
    T
    

    That looks better...