I have a macro that takes a body:
(defmacro bla开发者_高级运维h [& body] (dostuffwithbody))
But I'd like to add an optional keyword argument to it as well, so when called it could look like either of these:
(blah :specialthingy 0 body morebody lotsofbody)
(blah body morebody lotsofboy)
How can I do that? Note that I'm using Clojure 1.2, so I'm also using the new optional keyword argument destructuring stuff. I naively tried to do this:
(defmacro blah [& {specialthingy :specialthingy} & body])
But obviously that didn't work out well. How can I accomplish this or something similar?
Something like the following perhaps (also see how defn
and some other macros in clojure.core
are defined):
(defmacro blah [& maybe-option-and-body]
(let [has-option (= :specialthingy (first maybe-option-and-body))
option-val (if has-option (second maybe-option-and-body)) ; nil otherwise
body (if has-option (nnext maybe-option-and-body) maybe-option-and-body)]
...))
Alternatively you could be more sophisticated; it might be worthwhile if you think you might want to have multiple possible options at some point:
(defn foo [& args]
(let [aps (partition-all 2 args)
[opts-and-vals ps] (split-with #(keyword? (first %)) aps)
options (into {} (map vec opts-and-vals))
positionals (reduce into [] ps)]
[options positionals]))
(foo :a 1 :b 2 3 4 5)
; => [{:a 1, :b 2} [3 4 5]]
Michał Marczyk answered your question nicely, but if you're willing to accept (foo {} rest of the args), so a map with the optional keywords as the first argument (empty in this example), then this more simple code will work fine:
(defn foo [{:keys [a b]} & rest] (list a b rest))
(foo {} 2 3)
=> (nil nil (2 3))
(foo {:a 1} 2 3)
=> (1 nil (2 3))
Works the same for defmacro
. The advantage of this is that you don't have to remember a special syntax for optional keyword parameters, and implement code to handle the special syntax (maybe other people who are going to call your macro are more used to specifying key-value pairs in a map than outside of it). The disadvantage is of course that you always have to provide a map, even when you don't want to provide any keyword arguments.
A small improvement when you want to get rid of this disadvantage is checking if you provided a map as the first element of your argument list:
(defn foo [& rest]
(letfn [(inner-foo [{:keys [a b]} & rest] (list a b rest))]
(if (map? (first rest))
(apply inner-foo rest)
(apply inner-foo {} rest))))
(foo 1 2 3)
=> (nil nil (1 2 3))
(foo {:a 2 :b 3} 4 5 6)
=> (2 3 (4 5 6))
精彩评论