6

多数の (~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 マクロ (または同様のもの) は可能ですか? 私はそれを機能させることができませんでした。

4

4 に答える 4

9

(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&ssreify-from-mapsatom-beanemit-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-beanmacroexpandreify*reify

ここでの考え方は、呼び出しで魔法のメソッドの生成をトリガーする名前 (シンボリック形式) のキーワードをキーにして、上記のemit-mapようなものを提供できるということです。魔法は、指定された;に関数として格納されている関数によって実行されます。関数への引数は、「暗黙」のマップ (基本的に、この特定の場合のアトムの名前のように、フォーム内のすべてのメソッド定義にアクセスできる必要があるすべての情報) の後に、"マジック メソッド指定子」をフォームに記述します。上記のように、シンボリック名ではなく、実際のキーワード -> 関数マップを確認する必要があります。そのため、実際にはリテラル マップ、他のマクロ内、または の助けを借りてのみ使用できます。emit-atom-g&ssreify-from-mapsemit-mapreify-from-mapsreify-from-mapsreify-from-mapseval

通常のメソッド定義は引き続き含めることができ、reifyそれらの名前に一致するキーがemit-map. 出力関数は、メソッド定義の seqables (ベクトルなど) を が期待する形式で返す必要がありますreify。このように、1 つの「マジック メソッド指定子」に対して複数のメソッド定義が返される場合は比較的単純です。' 本体で引数をandに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))])))))))

これは実行時にコンパイラーを呼び出します。これは多少コストがかかりますが、実装するインターフェイスのセットごとに 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
于 2010-12-14T01:29:01.890 に答える
4

ポイントは、独自の set-and-get マクロの前に展開されるマクロ自体であることを具体化することです。そのため、set-and-get アプローチは機能しません。したがって、reify 内の内部マクロの代わりに、reify を生成する「外側」のマクロも必要です。

于 2010-12-14T10:17:40.407 に答える
0

マクロを最初に強制的に展開することもできます。

(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)))
于 2011-02-13T21:28:05.227 に答える