宏中的unquoting参数不能按预期工作

时间:2014-02-22 14:55:16

标签: macros clojure

我有以下Clojure宏:

(defmacro with-model
  [ref & body]
  `(tx
     (let [ds# (when (vector? (first ~body)) (ffirst ~body))
           name# (when (vector? (first ~body)) (second (first ~body)))
           ~ref (model ds# name#)]
       (do ~@body))))

我试图像这样使用它:

(deftest with-model-test
  (with-model sandwich
    (let [nodes (-> sandwich .listObjects iterator-seq)]
      (is nodes))))

或者这个:

(deftest with-model-test
  (with-model sandwich [*ds* "named-model"]
    (let [nodes (-> sandwich .listObjects iterator-seq)]
      (is nodes))))

这个想法是sandwich现在应该引用Model,但我得到一个运行时异常:

Unable to resolve symbol: sandwich in this context

如果我在宏中(println ~ref),我会得到模型实例。如果我(println '~ref),我会sandwich。我应该怎么做呢?

1 个答案:

答案 0 :(得分:1)

with-model宏用作(with-model sandwich (let [node (-> sandwich)]))时的宏展开如下所示(删除了名称空间,缩短了绑定名称并进行了一些格式化):

(macroexpand-1 '(with-model sandwich (let [node (-> sandwich)])))

(tx
 (let [ds   (when (vector? (first ((let [node (-> sandwich)]))))
              (ffirst ((let [node (-> sandwich)]))))
       name (when (vector? (first ((let [node (-> sandwich)]))))
              (second (first ((let [node (-> sandwich)])))))
       sandwich (model ds name)]
  (let [node (-> sandwich)])))

正如您所看到的,sandwichlet被定义之前被使用,因为宏生成的代码可以解释扩展后的第二个参数。解决这个问题的一种方法是让宏在扩展之前解决问题。一般来说,我尝试这样做是为了进行更简单的扩展,即使它有时意味着更复杂的宏代码,而不是在这种情况下很难。

(defmacro with-model
  [ref & [x & _ :as body]]
  `(tx
     (let [ds# ~(when (vector? x) (first x))
           name# ~(when (vector? x) (second x))
           ~ref (model ds# name#)]
       ~@body)))