Take the 2-minute tour ×

Dear all, I now have a preliminary macro

(defmacro key-if(test &key then else)
  `(cond (,test

and it is now correctly working as

> (key-if (> 3 1) :then 'ok)


> (key-if (< 5 3) :else 'ok)


> (key-if (> 3 1) :else 'oops)


> (key-if (> 3 1) :else 'oops :then 'ok)


Now I want to extend it a bit, which means I want to have arbitrary number of arguments followed by :then or :else (the keyword), so it will work like

> (key-if (> 3 1) :then)


> (key-if (> 3 1) :else 'oops :then (print 'hi) 'ok)



so I am now stuck on this point, I am a bit new to Lisp macros. I could think of using &rest for this extension, but don't know how, so I really need your idea on how to get this extension to work.

Thanks a lot.

share|improve this question
add comment

2 Answers

up vote 2 down vote accepted

I assume that you are using some Common Lisp implementation.

That style of argument parsing is not directly supported by the standard lambda lists used by DEFMACRO. You are right in thinking that you will have to parse the arguments yourself (you could use (test &rest keys-and-forms) to capture TEST, but extracting the :ELSE and :THEN parts would be up to you.

I am no super-(Common-)Lisper, but the syntax you are inventing here seems very non-idiomatic. The first hint is that macro lambda lists do not support what you want. Also, there are already standard alternatives that are the same length or shorter in raw characters typed (the typing overhead can be reduced a bit by using a structure editor like paredit in an Emacs).

(key-if blah :then foo bar)                       ; multiple then, no else
(when blah foo bar)                               ; standard

(key-if blah :else baz quux)                      ; no then, multiple else
(unless blah baz quux)                            ; standard

(key-if blah :then foo :else baz quux)            ; one then, multiple else
(if blah foo (progn baz quux))                    ; standard
(cond (blah foo) (t baz quux))

(key-if blah :then foo bar :else baz)             ; multiple then, one else
(if blah (progn foo bar) baz)                     ; standard
(cond (blah foo bar) (t baz))

(key-if blah :then foo bar :else baz quux)        ; multiple then, multiple else
(if blah (progn foo bar) (progn baz quux))        ; standard
(cond (blah foo bar) (t baz quux))                ; even shorter

(key-if blah :else baz quux :then foo bar)        ; multiple else before multiple then
(if (not blah) (progn baz quux) (progn foo bar))  ; only standard variation that is longer
(cond ((not blah) baz quux) (t foo bar)           ; standard, shorter

Common Lisp macros are very powerful code templating engines, but this particular usage makes me think that you are just not quite comfortable with the standard styles and idioms in Common Lisp. No matter which language you are using, it is almost always a good idea to “go with the flow” by adopting the ‘local’ styles and idioms instead of bringing styles and idioms from other languages with which you are more familiar.

share|improve this answer
The LOOP macro in Common Lisp has something like this. (loop ... if (foo) then do ... else do ...) and lots of variants of this. –  Rainer Joswig Nov 21 '09 at 15:58
@RainerJoswig which is why loop is widely hated by a large group of Lispers; it is a core macro in the standard that is completely at odds with the idioms of the rest of the language. Loop remains, however, a shining example of the power of lisp's macro system. –  OpenLearner Oct 29 '13 at 9:16
@OpenLearner: there were some complaints from some purists and from people who say that it is underspecified for some complex cases, but actual programmers either use it or use something else (some use the better designed, but even more complex ITERATE). LOOP is not much worse than other proposed iteration facilities, which are/were also widely different than the rest of the language. It's also much less at odds with the language idioms compared to something like FORMAT. –  Rainer Joswig Oct 29 '13 at 20:14
add comment

The &KEY mechanism only works on pairs. So you only have two paths: continue using pairs (you may use lists for the then and else arguments, and put these lists into PROGNs), or do you own parser on &REST.

Here is a parser on &REST:

(defmacro keyif (test &rest rest)
  (loop with then? = t
        for arg in rest
        if (eq arg :then) do (setf then? t)
        else if (eq arg :else) do (setf then? nil)
        else if then? collect arg into then-list
        else collect arg into else-list
        finally (return `(if ,test (progn ,@then-list) (progn ,@else-list)))))

It loops on the rest list, collecting the args into either then-list or else-list, depending on the then? flag, which is changed when the keywords :then and :else are found. As a bonus, multiple then/else keywords are supported, and :then is assumed by default.

share|improve this answer
add comment

Your Answer


