2010-12-14 2 views
6

呼んで具体化。私はコードの量を減らすためにマクロを使うのがいいと思った。だからではなく、は、自動的に内部のゲッターとセッターを作成するために、Clojureのマクロを使用して、私は、多くの(〜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)) 

を書くことができるようにしたい、このセットと-取得マクロ(または似たような)ことは可能ですか?私はそれを働かせることができませんでした。

答えて

9

(第二のアプローチで更新 - 第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&ssreify-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 
+0

私はChouserが基本的に同じような種類のevalをSOで使用していることを実証したと思っていましたが、確かに、[ここにある](http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-from -string-class-name/3752276#3752276)。検討中のシナリオは異なるが、関係するパフォーマンスのトレードオフについての彼の説明は、現在の状況に非常に関連している。 –

+0

うわー。素晴らしい答えをありがとう。 –

4

点があること具体化であるあなた自身の前に展開されるマクロそのものセットと-getのマクロ - ので、セットと-getのアプローチは動作しません。したがって、reify内部のマクロの代わりに、reifiedを生成する "外部"マクロも必要です。

+0

それは良い点です。ありがとう。 –

0

またforce your macro to expand firstに試すことができます。

(ns qqq (:use clojure.walk)) 
(defmacro expand-first [the-set & code] `(do [email protected](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))) 
0

トリックはがそれを見て、より一般的な解決策は、これらの線に沿って何か可能性が具体化する前に体を拡大することですので:

(defmacro reify+ [& body] 
    `(reify [email protected](map macroexpand-1 body))) 
関連する問題