Say I want to make a Clojure macro that does the following:
If x is a list calling the function "bar"
return :foobar
else
return x as a string
However, bar
is not defined; rather, it is only used internally in the macro, like so:
(foo (bar))
:foobar
(foo 1)
"1"
One could do something like this:
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
(str x)))
This works great for the (bar)
case, as well as for literals. However, symbols do not work as intended, giving the symbol name instead of its associated value:
user=> (def y 2)
#'user/y
user=> (foo y)
"y"
One could call the eval
function on x
before passing it to str
, but this causes problem when using the function in let
:
user=> (let [a 3 b (foo a)] b)
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:89)
Presumably, the problem has to do with symbol resolution, so maybe we try to work something out with syntax-quote:
(defma开发者_StackOverflow社区cro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(str ~x)))
Now, the problem is with (foo (bar))
, as this expands the else clause to (clojure.core/str (bar))
, which throws an exception, as bar
is not defined. I then tried doing some shenanigans with eval
:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(eval '(str ~x))))
But this doesn't work with let
bindings again:
user=> (let [a 1 b (foo a)] b)
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:153)
So I'm really at a loss here. It seems as though fixing one problem breaks another. Is there a better, simpler way of making this macro such that it works in the following cases:
- In
let
bindings - With
(bar)
- With symbols
P.S. If anybody is curious as to why I want to do this, I'm working on a DSL for Yahoo's YQL service and I want to be able to do things like (select (table :t) ...)
, but I need to be able to pass in symbols, as well as literals.
I believe this should work.
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
`(str ~x)))
精彩评论