Reputation: 732
I'm having some trouble writing macros which use destrucutred arguments. Here is an example:
(defmacro defny
[n args & forms]
`(defn ~n ~args ~@forms))
(defmacro defnz
[n f args & forms]
`(defn ~n ~args
(do
(~f ~@args)
~@forms)))
(defny y
[{:keys [value] :as args}]
(println "Y ARGS" args)
(println "Y VALUE" value))
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
Here, I have two macros, defny which simply calls through to defn, and defnz, which does the same, but additionally accepts another function which invokes prior to the function body with defnz's args.
When I invoke z, I expect to see both values and args printed out the same, but instead I get:
(z {:value 1})
Y ARGS {:keys [1], :as {:value 1}}
Y VALUE nil
Z ARGS {:value 1}
Z VALUE 1
=> nil
I can see why this is happening, the destructured args {:keys [1] :as {:value 1}} are getting passed to y, but I'm not sure how to fix the macro defnz so that the destructured args can be passed in properly.
Upvotes: 0
Views: 336
Reputation: 17859
you can easily see the mistake with macroexpansion:
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
expands to:
(defn z [{:keys [value], :as args}]
(do
(y [{:keys [value], :as args}])
(println "Z ARGS" args)
(println "Z VALUE" value)))
you are correct: you pass the whole args form to y, but what you need is make that line to be expanded to (y args)
where args
is just a plain symbol. To do it within macro (which turns symbols no namespace qualified ones) you should use "quote-unquote" trick:
(defmacro defnz
[n f args & forms]
`(defn ~n ~args
(do
(~f ~'args)
~@forms)))
now the expansion would be correct:
(defn z [{:keys [value], :as args}]
(do (y args) (println "Z ARGS" args) (println "Z VALUE" value)))
but it is rather bad idea, since you don't know exactly what allargs name would be passed to defnz
, like {:keys [value] :as all-of-them} would fail (because defnz
expects it to be args
. You can fix it by dynamically retrieving allargs name in defnz
:
(defmacro defnz
[n f [a :as args] & forms]
(let [a (if (:as a) a (assoc a :as 'everything))]
`(defn ~n ~[a]
(do
(~f ~(:as a))
~@forms))))
so now it would expand to the following:
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
;;(defn z [{:keys [value], :as args}]
;; (do (y args) (println "Z ARGS" args) (println "Z VALUE" value)))
(defnz z y
[{:keys [value] :as all-args}]
(println "Z ARGS" all-args)
(println "Z VALUE" value))
;;(defn z [{:keys [value], :as all-args}]
;; (do
;; (y all-args)
;; (println "Z ARGS" all-args)
;; (println "Z VALUE" value)))
(defnz z y
[{:keys [value]}]
(println "Z ARGS" everything)
(println "Z VALUE" value))
;;(defn z [{:keys [value], :as everything}]
;; (do
;; (y everything)
;; (println "Z ARGS" everything)
;; (println "Z VALUE" value)))
Upvotes: 2