2009-11-10 23 views
14

Estoy intentando escribir una macro que llamará a los métodos de configuración de Java en función de los argumentos que se le otorguen.Llamadas a un método dinámico en una macro Clojure?

Así, por ejemplo:

(my-macro login-as-fred {"Username" "fred" "Password" "wilma"}) 

podría ampliar a algo como lo siguiente:

(doto (new MyClass) 
    (.setUsername "fred") 
    (.setPassword "wilma")) 

¿Cómo recomendaría hacer frente a esto?

Específicamente, tengo problemas para encontrar la mejor manera de construir el nombre del método setter y hacer que lo interprete como un símbolo en la macro.

+0

Está seguro de querer llamar 'doto' con una clase como primer argumento? Harás cosas para el objeto Class en lugar de una instancia de esa clase. –

+0

Ah, gracias, eso fue un error tipográfico. Lo he corregido ahora. – npad

Respuesta

20

Lo bueno de las macros es que en realidad no tienes que profundizar en las clases ni nada de eso. Solo tiene que escribir el código que genera las expresiones s apropiadas.

primer lugar una función para generar una s-expresión como (.setName 42)

(defn make-call [name val] 
    (list (symbol (str ".set" name) val))) 

luego una macro para generar las expresiones y el tapón (~ @) ellos en una expresión doto.

(defmacro map-set [class things] 
    `(doto ~class [email protected](map make-call things)) 

Debido a que es una macro que nunca tiene que saber qué clase de lo que está siendo llamado a es ni siquiera que existe la clase en la que se va a utilizar.

+0

No funciona. Por un lado, no puede tomar el valor de '.' así. –

+0

arreglado para incluir los cambios de Brian Carper –

2

Usted tiene que morder la bala y utilizar clojure.lang.Reflector/invokeInstanceMethod así:

(defn do-stuff [obj m] 
    (doseq [[k v] m] 
    (let [method-name (str "set" k)] 
     (clojure.lang.Reflector/invokeInstanceMethod 
     obj 
     method-name 
     (into-array Object [v])))) 
    obj) 

(do-stuff (java.util.Date.) {"Month" 2}) ; use it 

No hay necesidad de una macro (por lo que yo sé, una macro no permitiría eludir la reflexión, o bien, al menos para el caso general).

+0

El macro no elude la reflexión en el arrendamiento, no en espíritu. genera s-expresiones ciegamente sin tener en cuenta el hecho de que pasan a clases de referencia. –

+0

Usar la reflexión para esto es bastante vil. Las macros y la reflexión son herramientas pesadas que debería pensar dos veces antes de usar, pero si puede especificar los campos para establecer como literales, las macros parecen mucho mejores. – amalloy

4

Alguien (creo que Arthur Ulfeldt) tenía una respuesta publicada que era casi correcta, pero se ha eliminado ahora. Por favor, acéptelo en vez del mío si vuelve a publicar. (O acepte de PMF.) Esta es una versión de trabajo:

(defmacro set-all [obj m] 
    `(doto ~obj [email protected](map (fn [[k v]] 
         (list (symbol (str ".set" k)) v)) 
        m))) 

user> (macroexpand-1 '(set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009})) 
(clojure.core/doto (java.util.Date.) (.setMonth 0) (.setDate 1) (.setYear 2009)) 

user> (set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009}) 
#<Date Fri Jan 01 14:15:51 PST 3909> 
+0

ok, lo he quitado. –

5

Por favor, no construyen s-expresiones con list para las macros. Esto dañará seriamente la seguridad de la macro. Es muy fácil cometer un error, que es difícil de rastrear. Por favor use siempre syntax-quote! Aunque, esto no es un problema en este caso, es bueno adquirir el hábito de usar solo sintaxis-quote!

Dependiendo de la fuente de su mapa, también podría considerar el uso de palabras clave como claves para que se vea más parecido a un clojure. Aquí es mi opinión:

(defmacro configure 
    [object options] 
    `(doto ~object 
    [email protected](map (fn [[property value]] 
       (let [property (name property) 
        setter (str ".set" 
            (.toUpperCase (subs property 0 1)) 
            (subs property 1))] 
       `(~(symbol setter) ~value))) 
      options)))

Esto puede ser utilizado como:

user=> (macroexpand-1 '(configure (MyClass.) {:username "fred" :password "wilma"})) 
(clojure.core/doto (MyClass.) (.setUsername "fred") (.setPassword "wilma"))
+0

No estoy de acuerdo. Usar 'list' puede ser peligroso si no entiendes lo que está sucediendo, pero si te limitas a usar' sintaxis-quote' es mucho más fácil perder el contacto con cómo funcionan las macros internamente, y creer que 'syntax-quote 'es la única herramienta de macroconstrucción. Una cosa que * * desaconsejaré es '(map (fn [[x y]] ...) coll)'; esto se expresa más limpiamente con '(para [[x y] coll] ...)'. – amalloy

+0

Sí, no uso con la frecuencia suficiente. Sin embargo, estoy de acuerdo con mi opinión sobre la lista. Es fácil hacer una cotización contra un error de retroceso. Y se hace con la suficiente frecuencia en la naturaleza. Incluso en clojure.core y contrib por personas que saben cómo funcionan las macros. syntax-quote simplemente elimina una clase completa de errores. Siempre es mejor que arreglarlo después del hecho. – kotarak

+0

Ok. "clase de errores" es tal vez un poco demasiado. Pero hace explícita la captura de símbolos en lugar de un accidente. – kotarak

Cuestiones relacionadas