1

我有这个示例代码,我在其中通过迭代映射的键值对来创建变量。

(defmacro block [bindings & body] 
  `(let [
    ~@(mapcat (fn [[k v]] [(if (symbol? k) k (symbol (name k))) `~v]) bindings) ]
  ~body))

(block {:a 1 :b 2} (if true (prn "true" a b) (prn "false")))

它工作正常。输出:“真实”1 2


但是现在我想传递与 var 相同的映射,但是它抛出了一个异常。

IllegalArgumentException 不知道如何从:clojure.lang.Symbol 创建 ISeq

(def ctx {:a 3 :b 4})

(block ctx (if true (prn "true" a b) (prn "false")))
4

1 回答 1

5

当您传递一个引用包含映射的 var 的符号时,它不起作用的原因是因为宏只看到符号文字 - 而不是它所引用的值。当您将地图文字传递给它时,宏会看到地图文字。

如果您希望它也支持符号,则需要resolve符号所指的 var 并获取其值,例如(var-get (resolve bindings))

(defmacro block [bindings & body]
  `(let [~@(mapcat (fn [[k v]] [(if (symbol? k)
                                  k
                                  (symbol (name k))) `~v])
                   (cond
                     (map? bindings) bindings
                     (symbol? bindings) (var-get (resolve bindings))
                     :else (throw (Exception. "bindings must be map or symbol"))))]
     ~@body))

(block ctx (if true (prn "true" a b) (prn "false")))
"true" 3 4
(block {:a 3 :b 4} (if true (prn "true" a b) (prn "false")))
"true" 3 4

您还需要使用“拼接”body到表单中~@,因为body即使它只包含一个表单,它也会是一系列表单。

它还可以帮助您查看宏在进行故障排除时如何扩展:

(macroexpand-1 '(block ctx (if true (prn "true" a b) (prn "false"))))
=> (clojure.core/let [a 3 b 4] (if true (prn "true" a b) (prn "false")))
于 2019-02-18T14:07:50.910 回答