(Actualizado con un segundo enfoque - véase más adelante la segunda regla horizontal - así como algunas observaciones explicativas re: la primera.)
Me pregunto si esto podría ser un paso en la direcci ón correcta que la macro atom-bean
pasa el valor de en tiempo de compilación de emit-atom-g&ss
al reify-from-maps
. Una vez que se compila un formulario particular atom-bean
, los cambios posteriores a emit-atom-g&ss
no tienen ningún efecto sobre el comportamiento del objeto creado.
Un ejemplo macroexpansion de la REPL (con algunos saltos de línea y indentación añade para mayor claridad):
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)))
Dos macroexpand-1
s son necesarios, porque atom-bean
es una macro que se expande a una llamada macro más. macroexpand
no sería particularmente útil, ya que ampliaría todo el camino hasta una llamada al reify*
, el detalle de implementación detrás de reify
.
La idea aquí es que puede suministrar un emit-map
como emit-atom-g&ss
anterior, con una clave de palabras cuyos nombres (en forma simbólica) activarán la generación de métodos mágicos en las llamadas reify-from-maps
. La magia se realiza mediante las funciones almacenadas como funciones en el emit-map
dado; los argumentos para las funciones son un mapa de "implicits" (básicamente toda y toda información que debe ser accesible para todas las definiciones de métodos en una forma reify-from-maps
, como el nombre del átomo en este caso particular) seguido por los argumentos que se le dieron al "especificador de método mágico" en el formulario reify-from-maps
. Como se mencionó anteriormente, reify-from-maps
necesita ver una palabra clave real -> mapa de funciones, no su nombre simbólico; por lo tanto, solo es realmente utilizable con mapas literales, dentro de otras macros o con la ayuda de eval
.
Las definiciones de métodos normales todavía se pueden incluir y se tratarán como en un formulario normal reify
, siempre que las claves que coincidan con sus nombres no aparezcan en el emit-map
. Las funciones de emisión deben devolver seqables (por ejemplo, vectores) de las definiciones de métodos en el formato esperado por reify
: de este modo, el caso con múltiples definiciones de método devueltas para un "especificador de método mágico" es relativamente simple. Si el argumento iface
se reemplazó por ifaces
y ~iface
con [email protected]
en el cuerpo de reify-from-maps
, se podrían especificar múltiples interfaces para la implementación.
Aquí hay otro enfoque, posiblemente más fáciles de razonar acerca de:
(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))])))))))
Este pide al compilador en tiempo de ejecución, que es un poco caro, pero sólo hay que hacer una vez al conjunto de interfaces para ser implementado. El resultado es una función que toma un átomo como argumento y reifica un contenedor alrededor del átomo que implementa las interfaces dadas con getters y setters como se especifica en el argumento get-set-map
. (Escrito de esta manera, esto es menos flexible que el enfoque anterior, pero la mayor parte del código anterior podría ser reutilizado aquí.)
Aquí está una interfaz de muestra y un/mapa colocador captador:
(definterface IFunky
(getFoo [])
(^void setFoo [v])
(getFunkyBar [])
(^void setWeirdBar [v]))
(def gsm
'{:foo [getFoo setFoo]
:bar [getFunkyBar setWeirdBar]})
Y algunos interacciones 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
Pensé que recordaba que Chouser demostró básicamente el mismo tipo de uso de 'eval' en SO y, por supuesto, [aquí está] (http://stackoverflow.com/questions/3748559/clojure-creating-new-instance-from -string-class-name/3752276 # 3752276). El escenario bajo consideración allí es diferente, pero su explicación del compromiso de desempeño involucrado es muy pertinente a la situación actual. –
Wow. Gracias por la excelente respuesta. –