(用第二種方法更新 - 見第二水平線以下 - 以及一些解釋性說明重新:第一個。)
我不知道這可能是一個一步在正確的方向:
(defmacro reify-from-maps [iface implicits-map emit-map & ms]
`(reify ~iface
[email protected](apply concat
(for [[mname & args :as m] ms]
(if-let [emit ((keyword mname) emit-map)]
(apply emit implicits-map args)
[m])))))
(def emit-atom-g&ss
{:set-and-get (fn [implicits-map gname sname k]
[`(~gname [~'this] (~k @~(:atom-name implicits-map)))
`(~sname [~'this ~'v]
(swap! ~(:atom-name implicits-map) assoc ~k ~'v))])})
(defmacro atom-bean [iface a & ms]
`(reify-from-maps ~iface {:atom-name ~a} ~emit-atom-g&ss [email protected]))
NB。 atom-bean
宏將emit-atom-g&ss
的實際編譯時間的值傳遞到reify-from-maps
。一旦編譯了特定的atom-bean
表單,對emit-atom-g&ss
的任何後續更改都不會影響創建的對象的行爲。
從REPL一個例子宏擴展(有一些行和縮進添加爲清楚起見):
user> (-> '(atom-bean HugeInterface data
(set-and-get setX getX :x))
macroexpand-1
macroexpand-1)
(clojure.core/reify HugeInterface
(setX [this] (:x (clojure.core/deref data)))
(getX [this v] (clojure.core/swap! data clojure.core/assoc :x v)))
兩個macroexpand-1
s爲必要的,因爲atom-bean
是其擴展到另外的宏調用的宏。 macroexpand
將不會特別有用,因爲它會一直擴展到致電reify*
(reify
背後的實現細節)。
這裏的想法是,你可以提供一個emit-map
像上面emit-atom-g&ss
,通過他們的名字(符號形式)將觸發reify-from-maps
調用魔術方法產生的關鍵字關鍵字。神奇是由在給定的emit-map
中存儲爲函數的函數執行的;這些函數的參數是一個「implicits」的映射(基本上任何和所有的信息都應該可以通過一個reify-from-maps
表單中的所有方法定義來訪問,就像這個特定情況下的atom的名字一樣),接下來是哪個參數被賦予「魔術方法說明符」在reify-from-maps
表單中。如上所述,reify-from-maps
需要看到一個實際的關鍵字 - >功能圖,而不是它的符號名;所以,它只能在文字地圖,其他宏或eval
的幫助下使用。
正常的方法定義仍然可以包含,並將按照正規的reify
表格的形式處理,只要在emit-map
中不出現與其名稱匹配的名稱。 emit函數必須按照reify
所期望的格式返回方法定義的seqable(例如向量):這樣,對於一個「magic方法說明符」返回多個方法定義的情況相對比較簡單。如果iface
參數被ifaces
和~iface
替換爲reify-from-maps
的[email protected]
,則可以指定多個接口來實現。
這裏的另一種方法,可能更容易推理:
(defn compile-atom-bean-converter [ifaces get-set-map]
(eval
(let [asym (gensym)]
`(fn [~asym]
(reify [email protected]
[email protected](apply concat
(for [[k [g s]] get-set-map]
[`(~g [~'this] (~k @~asym))
`(~s [~'this ~'v]
(swap! ~asym assoc ~k ~'v))])))))))
這就要求編譯器在運行時,這是有點貴,但只需要每一套做一次的接口是實現。結果是一個函數,它將一個原子作爲參數,並根據參數get-set-map
中的規定,使用getter和setter實現給定接口的原子的包裝器。 (寫這樣一來,這比以前的方法靈活,但最上面的代碼在這裏可以重複使用。)
這裏有一個樣品接口和一個getter/setter方法地圖:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
有些REPL互動:
user> (def data {:foo 1 :bar 2})
#'user/data
user> (def atom-bean-converter (compile-atom-bean-converter '[IFunky] gsm))
#'user/atom-bean-converter
user> (def atom-bean (atom-bean-converter data))
#'user/atom-bean
user> (.setFoo data-bean 3)
nil
user> (.getFoo atom-bean)
3
user> (.getFunkyBar data-bean)
2
user> (.setWeirdBar data-bean 5)
nil
user> (.getFunkyBar data-bean)
5
我以爲我記得Chouser在SO上演示基本上同樣的'eval'的使用,當然,[這是它](http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-from -string類名/ 3752276#3752276)。所考慮的情況有所不同,但他對性能權衡的解釋與當前情況非常相關。 – 2010-12-14 07:30:27
哇。謝謝你的出色答案。 – 2010-12-15 06:13:25