通过宏传递破坏的args

时间:2017-01-12 05:15:49

标签: clojure macros destructuring

我在编写使用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可以正常传递。

1 个答案:

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