2012-09-25 55 views
5

這與Treat Clojure macro as a function中討論的問題類似,但在頂級答案中嘗試該方法時,出現錯誤。關於我的具體應用希望太多的信息是沒有必要的,因爲它是相當複雜的,但這裏是什麼,我試圖做一個蒸餾版本:Clojure宏作爲函數/'部分'爲宏?

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

我以前在這方面的宏:

(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) 

我有點把握的問題:宏展開在編譯時完成的,我想包括在運行時的值宏觀擴張,因此引用和不加引用的奇怪用法使得一切都設置恰到好處。我基本上想要一個宏的部分,我需要能夠在不同的命名空間中爲不同的宏重用這種機制,並且所有的可變分辨率都是正確的。這甚至有可能嗎?

回答

2

該問題是由Clojure在語法引用(反引號)表達式中解析符號的方式引起的。爲了避免無意的變量捕獲,Clojure總是將語法引用表達式中的符號解釋爲指向Vars(而不是當地人)。

您可以通過「滾動自己的」表單構建代碼來解決這個問題,相當於語法引用生成的代碼。這是罪醜陋,但它的作品...只是不要說我沒提醒你:

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

哇,這就像一個閃回我的Common Lisp的日子......

+0

BTW ,關於'syntax-quote'的有趣事實:它完全在讀者中實現。當讀者看到一個反引號時,它讀取下面的表格,然後遞歸地轉換成使用'seq','concat'和'list'的代碼。編譯器只能看到生成的'concat' /'list'代碼。 –