Clojure宏:从地图创建本地变量

这个问题已经在这里有了答案:            >            In Clojure, how to destructure all the keys of a map?                                    3个
我有这个示例代码,通过迭代映射的键值对来创建var.

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

工作正常.
输出:“ true” 1 2

但是现在我想传递与var相同的映射,但是它引发了异常.

IllegalArgumentException不知道如何从以下方法创建ISeq:clojure.lang.Symbol

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

(block ctx (if true (prn "true" a b) (prn "false")))
最佳答案
当您传递指向包含地图的var的符号时,它不起作用的原因是因为宏仅看到符号文字,而不是其所指向的值.当您将其传递给地图文字时,宏会看到地图文字.

如果您希望它也支持符号,则需要解析该符号所引用的var并获取其值,例如(var-get(解析绑定)):

(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

您还需要使用〜@将主体“拼接”到表单中,因为主体即使是仅包含一个表单,也将是一系列表单.

它也可以帮助您查看对宏进行故障排除时的扩展方式:

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

转载注明原文:Clojure宏:从地图创建本地变量 - 代码日志