通过宏传递解构的参数
Passing destructured args through macros
我在编写使用 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 的参数。
当我调用 z 时,我希望看到打印出相同的值和参数,但我却得到:
(z {:value 1})
Y ARGS {:keys [1], :as {:value 1}}
Y VALUE nil
Z ARGS {:value 1}
Z VALUE 1
=> nil
我明白为什么会这样,解构的参数 {:keys [1] :as {:value 1}} 正在传递给 y,但我不确定如何修复宏 defnz 以便解构后的args可以正常传入
你可以很容易地看到宏展开的错误:
(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
的 allargs 名称到底是什么,例如 {:keys [value] :as all-of-them} 会失败 (因为 defnz
期望它是 args
。您可以通过在 defnz
:
中动态检索 allargs 名称来修复它
(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))))
所以现在它将扩展为以下内容:
(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)))
我在编写使用 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 的参数。
当我调用 z 时,我希望看到打印出相同的值和参数,但我却得到:
(z {:value 1})
Y ARGS {:keys [1], :as {:value 1}}
Y VALUE nil
Z ARGS {:value 1}
Z VALUE 1
=> nil
我明白为什么会这样,解构的参数 {:keys [1] :as {:value 1}} 正在传递给 y,但我不确定如何修复宏 defnz 以便解构后的args可以正常传入
你可以很容易地看到宏展开的错误:
(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
的 allargs 名称到底是什么,例如 {:keys [value] :as all-of-them} 会失败 (因为 defnz
期望它是 args
。您可以通过在 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))))
所以现在它将扩展为以下内容:
(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)))