我在编写使用destrucutred参数的宏时遇到了一些麻烦。这是一个例子:
(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))
在这里,我有两个宏,defny只是调用defn,而defnz也是这样,但是另外接受另一个函数,它在函数体之前使用defnz' s args进行调用。
当我调用z时,我希望看到两个值和args打印出来的相同,但我得到了:
(z {:value 1})
Y ARGS {:keys [1], :as {:value 1}}
Y VALUE nil
Z ARGS {:value 1}
Z VALUE 1
=> nil
我可以看到为什么会发生这种情况,结构化的args {:keys [1]:as {:value 1}}传递给y,但我不确定如何修复宏defnz以便结构化的args可以正常传递。
答案 0 :(得分:2)
你可以很容易地看到宏扩展的错误:
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
扩展为:
(defn z [{:keys [value], :as args}]
(do
(y [{:keys [value], :as args}])
(println "Z ARGS" args)
(println "Z VALUE" value)))
你是对的:你将整个args形式传递给y,但你需要的是将该行扩展到(y args)
,其中args
只是一个简单的符号。要在宏中执行它(这会使符号没有名称空间限定的符号),你应该使用“quote-unquote”技巧:
(defmacro defnz
[n f args & forms]
`(defn ~n ~args
(do
(~f ~'args)
~@forms)))
现在扩展是正确的:
(defn z [{:keys [value], :as args}]
(do (y args) (println "Z ARGS" args) (println "Z VALUE" value)))
但这是一个相当糟糕的主意,因为你不确切地知道将哪些名称传递给defnz
,如{:keys [value]:as all-of-them}会失败(因为{ {1}}期望它为defnz
。您可以通过在args
中动态检索allargs名称来修复它:
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))))