Utilizzare una macro clojure per creare automaticamente getter e setter all'interno di una chiamata reificare

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

  •  09-10-2019
  •  | 
  •  

Domanda

Sto cercando di implementare un enorme interfaccia Java con numerosi metodi getter e setter (~ 50) (alcuni con i nomi irregolari). Ho pensato che sarebbe stato bello utilizzare una macro per ridurre la quantità di codice. Così, invece di

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

Voglio essere in grado di scrivere

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

E 'questo set-and-get macro (o qualcosa di simile) possibile? Non sono stato in grado di farlo funzionare.

È stato utile?

Soluzione

(Aggiornato con un secondo approccio - vedi sotto la seconda regola orizzontale - così come alcune osservazioni esplicativi re:. Il primo)


Mi chiedo se questo potrebbe essere un passo nella giusta direzione:

(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. che la macro atom-bean passa il tempo di compilazione reale Valore di emit-atom-g&ss a reify-from-maps. Una volta che una particolare forma atom-bean viene compilato, ogni modifica successiva emit-atom-g&ss hanno alcun effetto sul comportamento dell'oggetto creato.

Un esempio macroexpansion dal REPL (con alcune interruzioni di linea e rientro aggiunti per chiarezza):

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

??Due macroexpand-1s, perché atom-bean è una macro che si espande ad un ulteriore invito macro. macroexpand non sarebbe particolarmente utile, in quanto sarebbe espandere questa fino a una chiamata a reify*, dettaglio attuazione dietro reify.

L'idea è che si può fornire una emit-map come emit-atom-g&ss sopra, digitato con parole chiave i cui nomi (in forma simbolica) sarà innescare generazione metodo magico nelle chiamate reify-from-maps. La magia viene eseguita dalle funzioni memorizzate come funzioni nel data emit-map; gli argomenti alle funzioni sono una mappa di "impliciti" (in pratica qualsiasi e tutte le informazioni che devono essere accessibili a tutte le definizioni metodo in una forma reify-from-maps, come il nome dell'atomo in questo caso particolare) seguito da qualsiasi argomenti stata data al "metodo identificatore magico" in forma reify-from-maps. Come accennato in precedenza, reify-from-maps ha bisogno di vedere una parola chiave attuale -> mappa delle funzioni, non il suo nome simbolico; così, è veramente solo utilizzabile con mappe letterali, all'interno di altre macro o con l'aiuto di eval.

Definizioni metodo normale può ancora essere inclusa e sarà trattato come in una forma reify regolare, forniti chiavi corrispondenti loro nomi non si verificano nel emit-map. Le funzioni EMIT devono restituire seqables (ad esempio vettori) di definizioni dei metodi nel formato previsto da reify: in questo modo, nel caso di più definizioni metodo restituiti per uno "metodo specificatore magico" è relativamente semplice. Se l'argomento iface sono stati sostituiti con ifaces e ~iface con ~@ifaces in reify-from-maps' corpo, interfacce multiple possono essere specificate per l'attuazione.


Ecco un altro approccio, forse più facile ragionare su:

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

Questo chiede il compilatore in fase di esecuzione, che è un po 'costoso, ma ha solo bisogno di essere fatto una volta per set di interfacce da attuare. Il risultato è una funzione che prende un atomo come argomento e reifica un involucro intorno all'atomo implementando le interfacce fornite con getter e setter come specificato nell'argomento get-set-map. (Scritta in questo modo, è meno flessibile rispetto all'approccio precedente, ma la maggior parte del codice di cui sopra potrebbe essere riutilizzato qui.)

Ecco un interfaccia di campione e una mappa getter / setter:

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

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

e alcune interazioni 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

Altri suggerimenti

Il punto è reificare essendo una macro stessa che si espande prima il proprio set-and-get macro - in modo che il set-and-get approccio non funziona. Così, invece di un interno all'interno reificare macro, è necessario una macro sul "fuori" che genera la reificare, anche.

Si può anche provare a la macro a ampliare prima :

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

Dato che il trucco è quello di espandere il corpo prima di reificare lo vede, una soluzione più generale potrebbe essere qualcosa in queste righe:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top