Verwenden Sie einen clojure Makro automatisch erstellen Getter und Setter in einem reify Anruf

StackOverflow https://stackoverflow.com/questions/4434902

  •  09-10-2019
  •  | 
  •  

Frage

Ich versuche, eine große Java-Schnittstelle mit zahlreichen (~ 50) Getter und Setter-Methoden (einige mit unregelmäßigen Namen) zu implementieren. Ich dachte, es wäre schön, einen Makro zu verwenden, um die Menge an Code zu reduzieren. So anstelle von

(def data (atom {:x nil}))
(reify HugeInterface
  (getX [this] (:x @data))
  (setX [this v] (swap! data assoc :x v))) 

Ich möchte in der Lage sein schreiben

(def data (atom {:x nil}))
(reify HugeInterface
  (set-and-get getX setX :x))

Ist die Set-and-get-Makro (oder so ähnlich) möglich? Ich habe nicht in der Lage gewesen, um es Arbeit zu machen.

War es hilfreich?

Lösung

(aktualisiert mit einem zweiten Ansatz - unterhalb der zweiten horizontalen Regel sehen - sowie einige Erläuterungen re:. Die ersten)


Ich frage mich, ob dies ein Schritt in der richtigen Richtung:

(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. dass die atom-bean Makro die tatsächliche Kompilierung Zeit vergeht Wert von emit-atom-g&ss auf reify-from-maps. Sobald eine bestimmte atom-bean Form kompiliert wird, spätere Änderungen emit-atom-g&ss auf das Verhalten des erzeugten Objekts keine Wirkung haben.

Ein Beispiel macroexpansion aus dem REPL (mit einigen Zeilenumbrüche und Einzüge für Klarheit hinzugefügt):

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)))

Zwei macroexpand-1s sind notwendig, weil atom-bean ein Makro, das zu einem weiteren Makroaufruf erweitert. macroexpand wäre nicht besonders nützlich sein, da es diesen ganzen Weg zu einem Aufruf reify* erweitern würde, die Umsetzung Detail hinter reify.

Die Idee dabei ist, dass Sie eine emit-map wie emit-atom-g&ss oben liefern kann, verkeilt durch Schlüsselwörter, deren Namen (in symbolischer Form) wird in reify-from-maps Anrufe magische Methode Generation auslösen. Die Magie wird durch die Funktionen als Funktionen im gegebenen emit-map gespeichert ist, ausgeführt; die Argumente der Funktionen sind eine Karte von „implicits“ (im Grunde jede und alle Informationen, die für alle Methodendefinitionen in einer reify-from-maps Form, wie der Name des Atoms in diesem speziellen Fall zugänglich sein sollte), gefolgt von je nachdem welche Argumente die gegeben wurden „magische Methode Spezifizierer“ in der reify-from-maps Form. Wie oben erwähnt, muss reify-from-maps eine tatsächliche Schlüsselwort sehen -> Funktionskarte, nicht seinen symbolischen Namen; so ist es nur wirklich verwendbar mit wörtlichen Karten, innerhalb anderen Makros oder mit Hilfe von eval.

Standardmethode Definitionen noch aufgenommen werden können und werden, wie in einer regulären reify Form behandelt werden, sofern die Tasten ihre Namen passend treten nicht in den emit-map. Die Funktionen müssen Emit seqables zurückkehren (z.B. Vektoren) von Methodendefinitionen in dem Format, das von reify erwartet: auf diese Weise wird der Fall mit mehreren Methodendefinitionen für eine „magische Methode Spezifizierer“ zurückgegeben wird, ist relativ einfach. Wenn das Argument iface mit ifaces und ~iface mit ~@ifaces in reify-from-maps‘Körpern ersetzt wurde, können mehrere Schnittstellen für die Umsetzung festgelegt werden.


Hier ist ein weiterer Ansatz, möglicherweise leichten Grund zu:

(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))])))))))

Diese fordert den Compiler zur Laufzeit, die etwas teuer ist, muss aber nur getan werden einmal pro Satz von Schnittstellen implementiert werden. Das Ergebnis ist eine Funktion, die ein Atom als Argument und verdinglicht eine Hülle um das Atom die gegebenen Schnittstellen mit Getter Umsetzung und Setter wie im get-set-map Argumente angegeben. (Auf diese Weise geschrieben werden, dies ist weniger flexibel als der bisherige Ansatz, aber die meisten der oben stehenden Code könnte hier wieder verwendet werden.)

Hier ist ein Beispiel-Schnittstelle und eine Getter / Setter Karte:

(definterface IFunky
  (getFoo [])
  (^void setFoo [v])
  (getFunkyBar [])
  (^void setWeirdBar [v]))

(def gsm
  '{:foo [getFoo setFoo]
    :bar [getFunkyBar setWeirdBar]})

Und einige REPL Wechselwirkungen:

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

Andere Tipps

Der Punkt wird reify ein Makro selbst, die vor Ihrem eigenen Set-and-get erweitert wird Makro - so der Set-and-get Ansatz nicht funktioniert. Also, statt einem inneren Makro in reify, benötigen Sie einen Makro auf der „Außenseite“, die die reify erzeugen auch.

Sie können auch Kraft versuchen, Ihren Makro erweitert erste :

(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)))

Da der Trick ist, den Körper vor reify erweitern es sieht, eine allgemeinere Lösung etwas in diese Richtung sein könnte:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top