2009-06-12 15 views
19

Me doy cuenta de que la primera regla de Macro Club es No usar macros, por lo que la siguiente pregunta es más un ejercicio para aprender Clojure que cualquier otra cosa (Me doy cuenta de que esto no es necesariamente el mejor use de macros).Ayúdame a escribir una macro Clojure que agrega metadatos automáticamente a una definición de función

Quiero escribir una macro simple que actúa como un contenedor alrededor de una macro normal (defn) y termina añadiendo algunos metadatos a la función definida. Así que me gustaría tener algo como esto:

(defn-plus f [x] (inc x)) 

... ampliar a algo como esto:

(defn #^{:special-metadata :fixed-value} f [x] (inc x)) 

En principio esto no parece tan difícil para mí, pero me Tengo problemas para ver los detalles de obtener el [args] y otros formularios en la función definida para que sean analizados correctamente.

Como beneficio adicional, si es posible me gustaría que la macro sea capaz de manejar todas las formas dispares de definición (es decir, con o sin documentos, definiciones de aria múltiple, etc.). Vi algunas cosas en el paquete clojure-contrib/def que parecían posiblemente útiles, pero era difícil encontrar el código de muestra que las usaba.

+0

Good intro ... props principales para eso. – Kekoa

+0

¿Por qué no utilizar macros? ¿Estás pensando en el preprocesador C? – Svante

Respuesta

18

Actualizado:

La versión anterior de mi respuesta no era muy robusta. Esta parece ser una manera más simple y más adecuada de hacerlo, robado de clojure.contrib.def:

(defmacro defn-plus [name & syms] 
    `(defn ~(vary-meta name assoc :some-key :some-value) [email protected])) 

user> (defn-plus ^Integer f "Docstring goes here" [x] (inc x)) 
#'user/f 
user> (meta #'f) 
{:ns #<Namespace user>, :name f, :file "NO_SOURCE_PATH", :line 1, :arglists ([x]), :doc "Docstring goes here", :some-key :some-value, :tag java.lang.Integer}

#^{} y with-meta no son la misma cosa. Para una explicación de la diferencia entre ellos, vea la discusión de Rich en el Clojure mailing list. Es un poco confuso y ha aparecido un montón de veces en la lista de correo; ver también here por ejemplo.

Tenga en cuenta que def es una forma especial y que maneja los metadatos un poco extraño en comparación con algunas otras partes del idioma. Establece los metadatos del var que está def fing a los metadatos del símbolo que nombra a la var; esa es la única razón por la que lo anterior funciona, creo. Consulte la clase DefExpr en Compiler.java en la fuente Clojure si desea ver las agallas de todo.

Por último, en la página 216 de Programming Clojure dice:

Se debe evitar generalmente macros lector en expansiones de macro, ya que las macros lector se evalúan en tiempo de lectura, antes de que comience la expansión de macros.

+0

¡Interesante! Pero, ¿hay alguna manera de hacerlo de manera más funcional? Envolver un 'defn' en un' do' y luego modificar destructivamente sus metadatos me parece un poco extraño. Por otra parte, mis experimentos con el uso de la sintaxis '#^{: k: v}' desde dentro de un 'defmacro' han sido todos los fracasos hasta ahora ... –

+0

Hacer cualquier tipo de' def' no es una cosa muy funcional para do, ya que muta destructivamente el estado de una tabla de despacho global. :) Pero tienes razón, y he actualizado mi respuesta. Mi respuesta anterior estaba soltando los metadatos de la etiqueta #^Integer que una 'def' normalmente tomaría. –

+0

Gracias, esto es mucho más comprensible y esos enlaces parecen muy útiles, aunque todavía estoy un poco desconcertado acerca de por qué (macroexpand-1 '(defn-plus foo [bar] (baz))) no muestra el -meta tag –

Cuestiones relacionadas