(Mise à jour avec une seconde approche - voir ci-dessous la deuxième règle horizontale - ainsi que quelques explications re: la première.)
Je me demande si cela pourrait être une étape dans la bonne direction:
(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. que la macro atom-bean
transmet la valeur réelle à la compilation de emit-atom-g&ss
sur reify-from-maps
. Une fois qu'une forme particulière atom-bean
est compilée, toutes les modifications suivantes apportées à emit-atom-g&ss
n'ont aucun effet sur le comportement de l'objet créé.
Un exemple macroexpansion de la REMP (avec quelques sauts de ligne et indentation ajouté 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-1
s 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 s'étendrait à un appel à reify*
, le détail de l'implémentation derrière reify
.
L'idée ici est que vous pouvez fournir un emit-map
comme emit-atom-g&ss
ci-dessus, saisi par des mots-clés dont les noms (sous forme symbolique) déclencheront la génération de méthode magique en reify-from-maps
appels. La magie est effectuée par les fonctions stockées en tant que fonctions dans le emit-map
donné; les arguments aux fonctions sont une carte de "implicits" (essentiellement toutes les informations qui devraient être accessibles à toutes les définitions de méthodes dans un formulaire reify-from-maps
, comme le nom de l'atome dans ce cas particulier) suivi par les arguments qui ont été donnés au "Spécificateur de méthode magique" dans le formulaire reify-from-maps
. Comme mentionné ci-dessus, reify-from-maps
doit voir un mot-clé réel -> 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
.
Les définitions de méthode normales peuvent toujours être incluses et seront traitées comme dans un formulaire reify
normal, à condition que les clés correspondant à leur nom ne se trouvent pas dans le emit-map
. Les fonctions emit doivent renvoyer des seqables (par exemple des vecteurs) de définitions de méthode au format attendu par reify
: de cette manière, le cas avec plusieurs définitions de méthodes renvoyées pour un "spécificateur de méthode magique" est relativement simple. Si l'argument iface
a été remplacé par ifaces
et ~iface
avec [email protected]
dans le corps reify-from-maps
, plusieurs interfaces peuvent être spécifiées pour l'implémentation.
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 [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))])))))))
Cela appelle le compilateur lors de l'exécution, ce qui est un peu cher, mais ne doit être fait une fois par ensemble d'interfaces pour être mis en œuvre. Le résultat est une fonction qui prend un atome comme argument et réifie un wrapper autour de l'atome implémentant les interfaces données avec les getters et setters comme spécifié dans l'argument get-set-map
. (Écrit de cette façon, ce qui est moins souple que la précédente approche, mais la plupart du code ci-dessus pourrait être réutilisé ici.)
Voici une interface d'échantillon et un getter/carte setter:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
Et certains 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
Je pensais que je me souvenais de Chouser démontrant fondamentalement le même genre d'utilisation de 'eval' sur SO et, bien sûr, [ici c'est] (http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-from -string-class-name/3752276 # 3752276). Le scénario à l'étude est différent, mais son explication du compromis de performance impliqué est très pertinente par rapport à la situation actuelle. –
Wow. Merci pour l'excellente réponse. –