5

这类似于将 Clojure 宏视为函数中讨论的问题,但是在尝试最佳答案中的方法时,我得到了一个错误。希望关于我的特定应用程序的太多信息是不必要的,因为它非常复杂,但这里是我尝试做的一个精简版本:

(defmacro make-fn [m arg1] 
    `(fn [& args#] 
      (eval `(~'~m ~'~arg1 ~@args#))))

我在这种情况下使用了宏:

(let [columns (make-columns table-width)
       table-name (keyword (str "table_" n))]
  (apply (make-fn helpers/tbl table-name) columns))

“helpers/tbl”是一个宏,它需要一个表名关键字和可变数量的包含列规范的列表(如 [:varchar 100] 或其他东西)。我正在尝试动态创建随机数据库表规范以方便一些测试。无论如何,在尝试执行上述代码时,我收到以下错误:

CompilerException java.lang.RuntimeException: Unable to resolve symbol: table-name in this context, compiling:(NO_SOURCE_PATH:1) 

我有点理解这个问题:宏扩展是在编译时完成的,我试图在宏扩展中包含一个运行时值,因此奇怪地使用引用和取消引用来让一切设置得恰到好处。我基本上想要一个宏的部分,并且我需要能够为不同命名空间中的不同宏重用这种机制,并让所有变量解析正确。这甚至可能吗?

4

1 回答 1

2

该问题是由 Clojure 解析语法引号(反引号)表达式中的符号的方式引起的。为了避免意外捕获变量,Clojure 总是将语法引用表达式中的符号解释为引用 Vars(而不是局部变量)。

你可以通过“滚动你自己的”表单构建代码来解决这个问题,相当于由 syntax-quote 生成的代码。它像罪恶一样丑陋,但它有效......只是不要说我没有警告你:

(defmacro make-fn [m arg1]
  (let [g (gensym)]
    (list 'fn ['& g]
      (list 'eval (list 'concat (list 'list m arg1) g)))))

哇,这就像回到我的 Common Lisp 时代......

于 2012-09-25T21:52:22.990 回答