tags:

views:

94

answers:

2

Dear all, I now have a preliminary macro

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

and it is now correctly working as

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

OK

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

OK

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

NIL

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

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)

NIL

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

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.

+2  A: 

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.

Chris Johnsen
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
+2  A: 

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.

Jerome