Clojure宏:从地图创建本地变量 [英] Clojure macro: Create local vars from a map
问题描述
我有这个示例代码,通过迭代映射的键值对来创建var。
I have this sample code where I create vars by iterating over the key value pair of the map.
(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")))
工作正常。
Ouput: true 1 2
It works fine. Ouput: "true" 1 2
但是现在我想通过与var相同的映射,但是,它会引发异常。
But now I want to pass the same map as a var but, it thows an exception.
IllegalArgumentException不知道如何从以下位置创建ISeq:clojure.lang.Symbol
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol
(def ctx {:a 3 :b 4})
(block ctx (if true (prn "true" a b) (prn "false")))
推荐答案
何时不起作用的原因您传递的符号指向包含地图的var,这是因为宏仅查看符号文字,而不是其所指向的值。当您将其传递给地图文字时,宏会看到地图文字。
The reason it doesn't work, when you pass a symbol that refers to a var that holds a map, is because the macro is only seeing the symbol literal — not the value it refers to. When you pass it a map literal, the macro is seeing the map literal.
如果您希望它也支持符号,则需要 resolve
符号所引用的var并获取其值,例如(var-get(解析绑定))
:
If you wanted it to also support symbols, you'll need to resolve
the var the symbol refers to and get its value e.g. (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
将是一系列表单,即使它只包含一个表单。
You also need to "splice" body
into the form using ~@
, because body
will be a sequence of forms even if it only contains one form.
它还可以帮助您查看对宏进行故障排除时的扩展方式:
It can also help to look at how your macro expands when troubleshooting it:
(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宏:从地图创建本地变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!