Utilice una macro para crear automáticamente clojure captadores y definidores en el interior de una llamada Reify

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

  •  09-10-2019
  •  | 
  •  

Pregunta

Estoy tratando de poner en práctica una gran interfaz Java con numerosos métodos getter y setter (~ 50) (algunos con nombres irregulares). Pensé que sería bueno utilizar una macro para reducir la cantidad de código. Así que en lugar de

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

Quiero ser capaz de escribir

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

¿Es este conjunto-y-get macro (o algo similar) posible? No he sido capaz de hacer que funcione.

¿Fue útil?

Solución

(Actualizado con un segundo enfoque - véase más adelante la segunda regla horizontal - así como algunas observaciones explicativas re:. El primero)


Me pregunto si esto podría ser un paso en la dirección correcta:

(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 pasa el tiempo de compilación actual valor de emit-atom-g&ss a reify-from-maps. Una vez que se compila una forma atom-bean en particular, cualquier cambio posterior a emit-atom-g&ss no tienen efecto sobre el comportamiento del objeto creado.

Un ejemplo macroexpansion de la REPL (con algunos saltos de línea y indentación añadido 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-1s 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 se expandiría esto hasta el final de una llamada a reify*, el detalle de implementación detrás reify.

La idea aquí es que se puede suministrar una emit-map como emit-atom-g&ss anteriormente, introducido por palabra clave cuyos nombres (en forma simbólica) dará lugar a la generación de método mágico de llamadas reify-from-maps. La magia se realiza mediante las funciones almacenadas como funciones en el emit-map dado; los argumentos de las funciones son un mapa de "implícitos" (básicamente, cualquier y toda la información que debe ser accesible a todas las definiciones de métodos en una forma reify-from-maps, como el nombre del átomo en este caso particular) seguidos por cualquiera argumentos fueron dadas a los "especificador método mágico" en forma reify-from-maps. Como se mencionó anteriormente, reify-from-maps necesita ver una palabra clave real -> Mapa de la función, no su nombre simbólico; así, es realmente sólo se puede usar con mapas literales, dentro de otras macros o con ayuda de eval.

Definiciones método normal todavía puede ser incluido y se trató como en una forma reify regular, previstas teclas que emparejan sus nombres no se producen en la emit-map. Las funciones Emit deben regresar seqables (por ejemplo, vectores) de definiciones de método en el formato esperado por reify: de esta manera, en el caso de múltiples definiciones de métodos devueltos por uno "especificador método mágico" es relativamente simple. Si el argumento iface fueron reemplazados por ifaces y ~iface con ~@ifaces en reify-from-maps' cuerpo, múltiples interfaces podrían especificarse para su 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 ~@ifaces
          ~@(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 a implementar. El resultado es una función que toma un átomo como un argumento y materializa una envoltura alrededor del átomo de aplicación de las interfaces de dados con captadores y definidores 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í hay una interfaz de muestra y un mapa de captador / definidor:

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

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

y algunas 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

Otros consejos

El punto es ser Reify una macro en sí que se expande antes de su puesta a punto y obtener macro - por lo que la puesta a punto y llegar enfoque no funciona. Así, en lugar de una macro dentro Reify interior, necesita una macro en el "exterior" que genera la Reify, también.

También puede tratar de a su macro expandir primero :

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

Desde el truco consiste en ampliar el cuerpo antes de Reify lo ve, una solución más general podría ser algo como lo siguiente:

(defmacro reify+ [& body]
  `(reify ~@(map macroexpand-1 body)))
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top