fizzer
fizzer

Reputation: 13796

How to pass optional macro args to a function

Clojure macro noob here. I have a function with some optional parameters, e.g.

(defn mk-foo [name & opt]
  (vec (list* name opt)))

giving this:

user> (mk-foo "bar" 1 2 3)
["bar" 1 2 3]

I'm trying to write a macro which takes the same optional arguments and passes them transparently to an invocation of mk-foo. So far I have this:

(defmacro deffoo [name & opt]
  `(def ~name ~(apply mk-foo (str name) opt)))

which has the desired effect:

user> (macroexpand '(deffoo bar 1 2 3))
(def bar ["bar" 1 2 3])

The use of apply to flatten the list opt feels clumsy. Is there an idiomatic way to do this? I'm guessing ~@ is needed, but I can't get the quoting right. Many thanks.

Upvotes: 3

Views: 636

Answers (1)

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

Your intuition about using apply served you well in this case. When you have a quoted form ` and then unqote all of them it can help to think about moving the un-quoting down to the smallest part or the list. This avoids using code to generate forms that could be simply written.

user=> (defmacro deffoo [name & opt] `(def ~name [~(str name) ~@opt]))       
#'user/deffoo
user=> (macroexpand '(deffoo "bar" 1 2 3))                            
(def "bar" ["bar" 1 2 3])

and here it is with the call to mk-foo:

(defmacro deffoo [name & opt] `(def ~name (mk-foo ~(str name) ~@opt)))     
#'user/deffoo
user=> (macroexpand '(deffoo "bar" 1 2 3))                                   
(def "bar" (user/mk-foo "bar" 1 2 3))

in this second case we move the ~ in one level and let the call to mk-foo stay quoted and only unquote the args required to build the parameter list (using splicing-unquote as you suspected)

Upvotes: 3

Related Questions