Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure macro as function / 'Partial' for Macros?

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?

like image 775
mephicide Avatar asked Nov 03 '22 15:11

mephicide


1 Answers

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

like image 187
Alex D Avatar answered Nov 13 '22 06:11

Alex D