Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dealing with symbol resolution properly in macros

Tags:

clojure

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:

(defmacro 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.

like image 881
Scott Avatar asked Oct 11 '22 08:10

Scott


1 Answers

I believe this should work.

(defmacro foo [x]
  (if (and (coll? x) (= (first x) 'bar))
    :foobar
    `(str ~x)))
like image 56
jamesnvc Avatar answered Oct 27 '22 00:10

jamesnvc