簡體   English   中英

使用clojure宏在reify調用中自動創建getter和setter

[英]Use a clojure macro to automatically create getters and setters inside a reify call

我正在嘗試使用大量(~50)getter和setter方法(一些具有不規則名稱)實現一個巨大的Java接口。 我認為使用宏來減少代碼量會很好。 而不是

(def data (atom {:x nil}))
(reify HugeInterface
  (getX [this] (:x @data))
  (setX [this v] (swap! data assoc :x v))) 

我希望能夠寫作

(def data (atom {:x nil}))
(reify HugeInterface
  (set-and-get getX setX :x))

這個set-and-get宏(或類似的東西)可能嗎? 我無法讓它發揮作用。

(更新了第二種方法 - 見下面第二條橫向規則 - 以及一些解釋性說明:第一條。)


我想知道這是否可能是朝着正確方向邁出的一步:

(defmacro reify-from-maps [iface implicits-map emit-map & ms]
  `(reify ~iface
     ~@(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 ~@ms))

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是必需的,因為atom-bean是一個宏,它擴展到另一個宏調用。 macroexpand不會是特別有用,因為它會擴大這種一路調用reify* ,實現細節背后的reify

這里的想法是你可以提供像emit-atom-g&ss一樣的emit-map ,由關鍵字鍵控,其名稱(符號形式)將在reify-from-maps調用中觸發魔術方法生成。 魔術由存儲為給定emit-map中的函數的函數執行; 函數的參數是一個“implicits”的映射(基本上是任何和所有信息都應該是reify-from-maps形式的所有方法定義都可以訪問,比如這個特殊情況下的原子名稱)后面跟着哪個參數在reify-from-mapsreify-from-maps中給出了“魔術方法說明符”。 如上所述, reify-from-maps需要查看實際的關鍵字 - >功能圖,而不是其符號名稱; 因此,它只適用於文字地圖,其他宏內或借助eval

正常的方法定義仍然可以包含在內,並且將被視為常規的reify形式,前提是匹配其名稱的鍵不會出現在emit-map 所述發送功能必須在由期望的格式返回的方法定義seqables(例如載體) reify :以這種方式,與多個方法定義的情況下返回一個“魔術方法指定符”是相對簡單的。 如果iface參數用替換ifaces~iface~@ifacesreify-from-maps的身體,多個接口可以為實現中指定。


這是另一種方法,可能更容易推理:

(defn compile-atom-bean-converter [ifaces get-set-map]
  (eval
   (let [asym (gensym)]
     `(fn [~asym]
        (reify ~@ifaces
          ~@(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

重點是作為一個宏本身,它在你自己的set-and-get宏之前擴展 - 所以set-and-get方法不起作用。 因此,在內部宏而不是內部宏中,你需要在“外部”上生成一個用於生成reify的宏。

由於訣竅是在reify看到它之前擴展身體,更通用的解決方案可能是這樣的:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))

您還可以嘗試強制您的宏首先展開

(ns qqq (:use clojure.walk))
(defmacro expand-first [the-set & code] `(do ~@(prewalk #(if (and (list? %) (contains? the-set (first %))) (macroexpand-all %) %) code)))

(defmacro setter [setterf kw] `(~setterf [~'this ~'v] (swap! ~'data assoc ~kw ~'v)))
(defmacro getter [getterf kw] `(~getterf [~'this] (~kw @~'data)))
(expand-first #{setter getter} 
 (reify HugeInterface 
  (getter getX :x)
  (setter setX :x)))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM