5
votes

This is similar to the problem discussed in Treat Clojure macro as a function but when trying the approach in the top answer, I got an error. Hopefully too much information about my specific application is not necessary, because it is quite complicated, but here is a distilled version of what I tried to do:

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

I used the macro in this context:

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

"helpers/tbl" is a macro that expects a table name keyword and a variable number of lists containing column specifications (like [:varchar 100] or something). I am trying to create random database table specifications on the fly to facilitate some testing. Anyway, when trying to execute the above code, I get the following error:

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

I sort of grasp the problem: macro expansion is done at compile-time, and I am trying to include a runtime value in the macro expansion, hence the odd use of quoting and unquoting to get everything set up just right. I basically want a partial for macros, and I need to be able to reuse this mechanism for different macros in different namespaces, and have all of the variable resolution come out right. Is this even possible?

1

1 Answers

2
votes

The problem is caused by the way Clojure resolves symbols within a syntax-quote (backtick) expression. To avoid unintentional variable capture, Clojure always interprets symbols within a syntax-quote expression as referring to Vars (not locals).

You can get around this by "rolling your own" form-building code, equivalent to that generated by syntax-quote. It's as ugly as sin, but it works... just don't say I didn't warn you:

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

Wow, this is like a flashback to my Common Lisp days...