Search code examples
macrosclojuredestructuring

How can I mix optional keyword arguments with the & rest stuff?


I have a macro that takes a body:

(defmacro blah [& 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?


Solution

  • 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]]