假设我想创建一个执行以下操作的Clojure宏:
If x is a list calling the function "bar"
return :foobar
else
return x as a string
但是,bar
未定义;相反,它只在宏内部使用,如下所示:
(foo (bar))
:foobar
(foo 1)
"1"
可以这样做:
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
(str x)))
这适用于(bar)
案例以及文字。但是,符号不能按预期工作,而是给出符号名称而不是其关联值:
user=> (def y 2)
#'user/y
user=> (foo y)
"y"
可以在eval
上调用x
函数,然后将其传递给str
,但这会导致在let
中使用该函数时出现问题:
user=> (let [a 3 b (foo a)] b)
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:89)
据推测,这个问题与符号解析有关,所以也许我们尝试使用syntax-quote来解决问题:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(str ~x)))
现在,问题在于(foo (bar))
,因为这会将else子句扩展为(clojure.core/str (bar))
,这会引发异常,因为bar
未定义。然后我尝试用eval
做一些恶作剧:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(eval '(str ~x))))
但这不再适用于let
绑定:
user=> (let [a 1 b (foo a)] b)
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:153)
所以我真的很茫然。看起来修复一个问题似乎打破了另一个问题。有没有更好,更简单的方法来制作这个宏,以便它在以下情况下起作用:
let
绑定(bar)
P.S。如果有人对为什么我想要这样做感到好奇,我正在为Yahoo的YQL服务开发DSL,我希望能够做(select (table :t) ...)
之类的事情,但我需要能够传递符号和文字。
答案 0 :(得分:4)
我相信这应该有用。
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
`(str ~x)))