Utilisez une macro clojure pour créer automatiquement des accesseurs dans un appel reify

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

  •  09-10-2019
  •  | 
  •  

Question

Je suis en train de mettre en œuvre une grande interface Java avec de nombreux (~ 50) getter et setter (certains avec des noms irréguliers). Je pensais que ce serait bien d'utiliser une macro pour réduire la quantité de code. Ainsi, au lieu de

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

Je veux être en mesure d'écrire

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

Est-ce set-et-get macro (ou quelque chose de similaire) possible? Je ne l'ai pas été en mesure de le faire fonctionner.

Était-ce utile?

La solution

(Mise à jour avec une seconde approche - voir ci-dessous la deuxième règle horizontale - ainsi que quelques explications re:. Le premier)


Je me demande si cela pourrait être un pas dans la bonne direction:

(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. que la macro atom-bean passe le temps réel compilation valeur de emit-atom-g&ss à reify-from-maps. Une fois qu'un formulaire atom-bean particulier est compilé, toute modification ultérieure de emit-atom-g&ss n'a aucun effet sur le comportement de l'objet créé.

Un exemple macroexpansion du REPL (avec quelques sauts de ligne et indentation ajoutée pour plus de clarté):

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

Deux macroexpand-1s sont nécessaires, car atom-bean est une macro qui se développe à un autre appel macro. macroexpand ne serait pas particulièrement utile, car il se développerait tout cela la voie à un appel à reify*, le détail de la mise en œuvre derrière reify.

L'idée ici est que vous pouvez fournir un emit-map comme emit-atom-g&ss ci-dessus, calée par des mots-clés dont les noms (sous forme symbolique) déclencherez génération de méthode magique dans les appels reify-from-maps. La magie est effectuée par les fonctions stockées sous forme de fonctions dans la emit-map donnée; les arguments aux fonctions sont une carte de « implicits » (essentiellement toute information qui devrait être accessible à toutes les définitions de méthode sous une forme de reify-from-maps, comme le nom de l'atome dans ce cas particulier), suivi par celui des deux arguments ont été donnés aux « spécificateur méthode magique » sous la forme de reify-from-maps. Comme mentionné ci-dessus, reify-from-maps a besoin de voir un mot-clé réelle -> carte de fonction, pas son nom symbolique; donc, il est seulement vraiment utilisable avec des cartes littérales, à l'intérieur d'autres macros ou avec l'aide de eval.

Définitions méthode normale peut être inclus encore et sera traitée comme une forme régulière de reify, clés fournies correspondant à leur nom ne se produisent pas dans le emit-map. Les fonctions doivent retourner Emit seqables (par exemple des vecteurs) des définitions de méthode dans le format attendu par reify: de cette façon, le cas avec plusieurs définitions de méthode pour un retour « spécificateur méthode magique » est relativement simple. Si l'argument iface ont été remplacés par ifaces et ~iface avec ~@ifaces dans reify-from-maps » corps, plusieurs interfaces peuvent être spécifiées pour la mise en œuvre.


Voici une autre approche, peut-être plus facile de raisonner sur:

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

invite le compilateur lors de l'exécution, ce qui est un peu cher, mais ne doit être fait une fois par ensemble d'interfaces à mettre en œuvre. Le résultat est une fonction qui prend un atome d'argument et réifie une enveloppe autour de l'atome mise en oeuvre des interfaces avec des données accesseurs tel que spécifié dans l'argument get-set-map. (Écrit de cette façon, ce qui est moins souple que l'approche précédente, mais la plupart du code ci-dessus pourrait être réutilisé ici.)

Voici une interface d'échantillon et une carte getter / setter:

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

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

Et quelques interactions 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

Autres conseils

Le point est d'être reify une macro elle-même qui est détendu avant votre propre jeu et-get macro - de sorte que le jeu et-get approche ne fonctionne pas. Ainsi, au lieu d'un intérieur reify macro intérieur, vous avez besoin d'une macro sur le « extérieur » qui génère le reify aussi.

Vous pouvez également essayer de votre macro premier_groupe_administratif :

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

Depuis l'astuce consiste à développer le corps avant reify le voit, une solution plus générale pourrait être quelque chose le long de ces lignes:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top