在宏中正确处理符号解析

Sco*_*ott 5 clojure

假设我想制作一个执行以下操作的Clojure宏:

If x is a list calling the function "bar"
  return :foobar
else
  return x as a string
Run Code Online (Sandbox Code Playgroud)

但是,bar没有定义; 相反,它只在宏内部使用,如下所示:

(foo (bar))
:foobar

(foo 1)
"1"
Run Code Online (Sandbox Code Playgroud)

人们可以这样做:

(defmacro foo [x] 
  (if (and (coll? x) (= (first x) 'bar)) 
      :foobar 
      (str x)))
Run Code Online (Sandbox Code Playgroud)

这适用于(bar)案例以及文字.但是,符号不能按预期工作,给出符号名称而不是其关联值:

user=> (def y 2)
#'user/y
user=> (foo y)
"y"
Run Code Online (Sandbox Code Playgroud)

可以在传递eval函数x之前调用该函数str,但这在使用函数时会导致问题let:

user=> (let [a 3 b (foo a)] b)
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:89)
Run Code Online (Sandbox Code Playgroud)

据推测,这个问题与符号解析有关,所以也许我们尝试用syntax-quote来解决问题:

(defmacro foo [x] 
  `(if (and (coll? '~x) (= (first '~x) '~'bar)) 
    :foobar 
    (str ~x)))
Run Code Online (Sandbox Code Playgroud)

现在,问题在于(foo (bar)),因为这会将else子句扩展为(clojure.core/str (bar))抛出异常,因为bar未定义.然后我尝试做一些恶作剧eval:

(defmacro foo [x] 
  `(if (and (coll? '~x) (= (first '~x) '~'bar)) 
    :foobar 
    (eval '(str ~x))))
Run Code Online (Sandbox Code Playgroud)

但这不再适用于let绑定:

user=> (let [a 1 b (foo a)] b)
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:153)
Run Code Online (Sandbox Code Playgroud)

所以我真的很茫然.看起来修复一个问题似乎打破了另一个问题.有没有更好,更简单的方法来制作这个宏,使其在以下情况下起作用:

  • let绑定中
  • (bar)
  • 有符号

PS如果有人对我为什么要这样做感到好奇,我正在研究用于Yahoo的YQL服务的DSL,我希望能够做类似的事情(select (table :t) ...),但我需要能够传递符号,以及文字.

jam*_*nvc 4

我相信这应该有效。

(defmacro foo [x]
  (if (and (coll? x) (= (first x) 'bar))
    :foobar
    `(str ~x)))
Run Code Online (Sandbox Code Playgroud)