(2 番目のアプローチで更新されました -- 以下の 2 番目の水平方向のルールを参照してください -- および最初のルールに関するいくつかの説明を参照してください。)
これが正しい方向への一歩になるのではないかと思います:
(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))
注意。atom-bean
マクロが on の実際のコンパイル時の値をに渡すこと。特定のフォームがコンパイルされると、その後の変更は、作成されたオブジェクトの動作には影響しません。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)))
はさらにマクロ呼び出しに展開されるマクロであるため、 2 つmacroexpand-1
の が必要です。これは、 の背後にある実装の詳細であるへの呼び出しまで拡張されるため、特に有用ではありません。atom-bean
macroexpand
reify*
reify
ここでの考え方は、呼び出しで魔法のメソッドの生成をトリガーする名前 (シンボリック形式) のキーワードをキーにして、上記のemit-map
ようなものを提供できるということです。魔法は、指定された;に関数として格納されている関数によって実行されます。関数への引数は、「暗黙」のマップ (基本的に、この特定の場合のアトムの名前のように、フォーム内のすべてのメソッド定義にアクセスできる必要があるすべての情報) の後に、"マジック メソッド指定子」をフォームに記述します。上記のように、シンボリック名ではなく、実際のキーワード -> 関数マップを確認する必要があります。そのため、実際にはリテラル マップ、他のマクロ内、または の助けを借りてのみ使用できます。emit-atom-g&ss
reify-from-maps
emit-map
reify-from-maps
reify-from-maps
reify-from-maps
eval
通常のメソッド定義は引き続き含めることができ、reify
それらの名前に一致するキーがemit-map
. 出力関数は、メソッド定義の seqables (ベクトルなど) を が期待する形式で返す必要がありますreify
。このように、1 つの「マジック メソッド指定子」に対して複数のメソッド定義が返される場合は比較的単純です。' 本体で引数をandに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 回だけ実行する必要があります。結果は、引数としてアトムを取り、引数で指定されたようにゲッターとセッターを使用して特定のインターフェイスを実装するアトムのラッパーを具体化する関数get-set-map
です。(このように書くと、以前のアプローチよりも柔軟性が低くなりますが、上記のコードのほとんどはここで再利用できます。)
サンプル インターフェイスと 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