Clojure Macroを使用して、具体的な呼び出し内にゲッターとセッターを自動的に作成します
質問
私は、多数の(〜50)ゲッターとセッターのメソッド(不規則な名前のあるもの)を持つ巨大な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))
このセットアンドゲットマクロ(または同様のこと)は可能ですか?私はそれを機能させることができませんでした。
解決
(2番目のアプローチで更新されます - 2番目の水平ルールの以下を参照 - およびいくつかの説明的な発言re:最初のもの。)
これが正しい方向への一歩かもしれないのだろうか:
(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
Sが必要です atom-bean
さらにマクロ呼び出しに拡張されるマクロです。 macroexpand
これを通話までずっと拡張するので、特に役に立ちません reify*
, 、背後にある実装の詳細 reify
.
ここでのアイデアは、あなたが供給できるということです emit-map
お気に入り emit-atom-g&ss
上記では、(象徴的な形で)名前が魔法のメソッドの生成をトリガーするキーワードによってキー付き reify-from-maps
電話。魔法は、指定された関数として保存されている関数によって実行されます emit-map
;関数の引数は、「Inflicits」のマップです(基本的に、すべてのメソッド定義がアクセスできるはずのすべての情報があります。 reify-from-maps
この特定のケースの原子の名前のように)フォームに続いて、その後に「魔法の方法仕様」に与えられた引数が与えられました。 reify-from-maps
形。上記のように、 reify-from-maps
象徴的な名前ではなく、実際のキーワード - >関数マップを表示する必要があります。したがって、それは文字通りのマップ、他のマクロ内、または eval
.
通常のメソッド定義は引き続き含めることができ、通常のように扱われます reify
フォーム、名前に一致するキーがそれらの名前に一致する場合は、 emit-map
. 。エミット関数は、次の形式でメソッド定義のseqables(例えばベクトル)を返す必要があります reify
: :このように、1つの「Magic Method Specifier」に対して返される複数のメソッド定義がある場合は比較的簡単です。場合 iface
議論は置き換えられました ifaces
と ~iface
と ~@ifaces
の reify-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))])))))))
これは、実行時にコンパイラに呼び出されますが、これはやや高価ですが、実装するインターフェイスのセットごとに1回だけ行う必要があります。結果は、原子を引数として取り、原子の周りのラッパーをreiferしている関数です。 get-set-map
口論。 (このように書かれていると、これは以前のアプローチよりも柔軟性が低くなりますが、上記のコードのほとんどはここで再利用できます。)
これがサンプルインターフェイスとゲッター/セッターマップです。
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
そしていくつかのリプルの相互作用:
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
他のヒント
ポイントは、あなた自身のセットとゲットのマクロの前に拡張されるマクロ自体であることを具体化することです。そのため、セットとゲットのアプローチは機能しません。したがって、具体的な内側のマクロの代わりに、「外側」にマクロが必要なマクロも必要です。
あなたも試すことができます マクロに最初に拡張するように強制します:
(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)))
トリックは以前に体を拡張することだからです 具体化 それを見て、より一般的な解決策はこれらの線に沿って何かである可能性があります:
(defmacro reify+ [& body]
`(reify ~@(map macroexpand-1 body)))