Reputation: 51
I have a problem with a pair of Clojure macros when a default argument is defined.
In the following situation with 2 macros, where mm02 calls mm01:
(defmacro mm01
[ & [ { :keys [ f1 ] :or { f1 long } :as opts } ]]
`(let []
(println "(2) ~f1" ~f1)))
(defmacro mm02
[ & [ { :keys [ f1 ] :as opts } ]]
`(let []
(println "(1) ~f1" ~f1)
(mm01 ~@opts)))
The evaluation of:
(mm02 { :f1 byte })
prints out:
(1) ~f1 #function[clojure.core/byte]
(2) ~f1 #function[clojure.core/long]
However, I would have expected:
(1) ~f1 #function[clojure.core/byte]
(2) ~f1 #function[clojure.core/byte]
Am I doing anything wrong or I am missing something?
By the way, the evaluation of:
(mm01 { :f1 byte })
prints out:
(2) ~f1 #function[clojure.core/byte]
Thank you very much.
Upvotes: 2
Views: 220
Reputation: 91897
~@
expands out a sequence of things into several individual things, inside of a syntax-quote context. Your opts
binding is a map, which is conceptually a sequence of map-entries. You can see this in action by playing around in the repl with the expressions your macros will generate: this is often a useful way to look at the intermediate steps of a macro, compared to debugging by trial-and-error on the macro as a whole.
user=> (let [opts {:f1 'long}]
#_=> `(foo ~@opts))
(user/foo [:f1 long])
See the square brackets around :f1 long
? That's the problem: your other macro expects to be called with a map, not a vector. As a result, the destructuring fails to find the key you were looking for. To fix this, just remove the @
and use an ordinary unquote, not a splicing unquote.
user=> (let [opts {:f1 'long}]
#_=> `(foo ~opts))
(user/foo {:f1 long})
As an additional improvement, you should replace the distracting [& [{...}]]
argument stanza with just [{...}]
. They behave the same except that the former allows the caller to pass zero arguments (filling in with nils), or any number of extra arguments, which all get ignored. Your version is very slightly more convenient for the caller if they meant to omit an argument and get defaults, but inevitably will lead them to a big hassle of debugging if they leave out an argument, or provide too many, accidentally.
Upvotes: 5