(第二のアプローチで更新 - 第2の水平ルールの下に表示さ - だけでなく、いくつかの説明の発言が再:最初の1)を
これはステップかもしれませんかしら右方向:
(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から例えばマクロ展開(一部改行やインデントとは、明確にするために追加):atom-bean
はさらに、マクロ呼び出しに展開されるマクロであるため
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は、必要です。 への呼び出しまでこれを拡張し、reify
の実装の詳細を拡張するので、macroexpand
は特に有用ではありません。
ここではのようなemit-map
を指定し、reify-from-maps
呼び出しで名前(記号形式)がマジックメソッドの生成をトリガーするキーワードをキーとしています。魔法は与えられたemit-map
の関数として保存された関数によって実行されます。関数への引数は "implicits"のマップです(基本的には、reify-from-maps
形式のすべてのメソッド定義にアクセス可能なすべての情報、この場合はアトムの名前など)。 reify-from-maps
フォームの "magic method specifier"です。上で述べたように、reify-from-maps
は実際のキーワード - >関数マップを見る必要があります。したがって、リテラルマップ、他のマクロ、またはeval
の助けを借りれば、本当に便利です。
普通のメソッド定義をそのまま含めることができ、通常のreify
フォームのように扱われます。ただし、名前に一致するキーはemit-map
には含まれません。 emit関数は、reify
で期待される形式のメソッド定義のseqables(例えばベクトル)を返す必要があります。このように、1つの "magic method specifier"に対して複数のメソッド定義が返される場合は比較的簡単です。 iface
引数はreify-from-maps
の体に[email protected]
でifaces
と~iface
に置き換えた場合には、複数のインタフェースを実装するために指定することができました。ここで
約推論しやすく、おそらく、別のアプローチです:
(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
引数で指定されているゲッターとセッターで、指定されたインタフェースを実装するアトムの周りのラッパーを再現する関数です。 (このように書かれ、これは以前のアプローチよりも少ない柔軟性があるが、上記のコードのほとんどは、ここで再利用することができます。)ここで
だサンプルインターフェースとゲッター/セッターマップ:
(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が基本的に同じような種類のevalをSOで使用していることを実証したと思っていましたが、確かに、[ここにある](http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-from -string-class-name/3752276#3752276)。検討中のシナリオは異なるが、関係するパフォーマンスのトレードオフについての彼の説明は、現在の状況に非常に関連している。 –
うわー。素晴らしい答えをありがとう。 –