通过宏传递解构的参数

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)))