Search code examples
lispcommon-lisplisp-macros

Build dynamic COND clauses in Common Lisp


I wonder if it is possible to dynamically build COND clauses from a loop like (pseudo code):

(defvar current-state 1)

(defmacro mymacro ()
  (cond
    `(loop (state . callback) in possible-states
      do ((eq current-state ,state)
          (funcall ,callback)))))

The LOOP would build the clauses from a list and generate something like:

(cond
  ((eq current-state 1)
   (funcall func-1))
  ((eq current-state 2)
   (funcall func-2))
  ((eq current-state 3)
   (funcall func-3)))

Solution

  • Macros are expanded at compile time, so your possible-states variable has to be a compile-time constant. If this is not the case (or if you are not absolutely clear on what I mean above), you should not use a macro here.

    Use a function instead:

    (funcall (cdr (find current-state possible-states :key #'car :test #'eq)))
    

    or

    (funcall (cdr (assoc current-state possible-states :test #'eq)))
    

    or, better yet, make your possible-states a hash table rather than an association list:

    (funcall (gethash current-state possible-states))
    

    However, if your possible-states is a compile time constant, you can, indeed, use a macro, except that you probably want to use case instead of cond:

    (defmacro state-dispatch (state)
      `(case ,state
         ,@(mapcar (lambda (cell)
                     `((,(car cell)) (,(cdr cell))))
                   possible-states)))
    (defparameter possible-states '((1 . foo) (2 . bar)))
    (macroexpand-1 '(state-dispatch mystate))
    ==> (CASE MYSTATE ((1) (FOO)) ((2) (BAR))) ; T
    

    Note that from the speed point of view, the gethash version is probably identical to the macro version (at the very least it is not slower).