2009-06-12 5 views
19

Je réalise que la première règle de Macro Club est Ne pas utiliser de macros, donc la question suivante est plus un exercice d'apprentissage de Clojure qu'autre chose (Je me rends compte que ce n'est pas nécessairement le meilleur utiliser de macros).Aidez-moi à écrire une macro Clojure qui ajoute automatiquement des métadonnées à une définition de fonction

Je veux écrire une macro simple qui agit comme une enveloppe autour d'une macro (defn) régulière et finit par ajouter quelques métadonnées à la fonction définie. Je voudrais donc avoir quelque chose comme ceci:

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

... étendre vers quelque chose comme ceci:

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

En principe, cela ne semble pas difficile pour moi, mais je J'ai du mal à clouer les détails pour obtenir le [args] et d'autres formes dans la fonction définie à analyser correctement. En prime, si possible, j'aimerais que la macro puisse gérer toutes les formes disparates de defn (ie, avec ou sans docstrings, définitions d'arité multiples, etc.). J'ai vu certaines choses dans le paquet clojure-contrib/def qui semblaient être utiles, mais il était difficile de trouver un exemple de code qui les utilisait.

+0

Bonne intro ... les principaux accessoires pour cela. – Kekoa

+0

Pourquoi ne pas utiliser des macros? Pensez-vous au préprocesseur C? – Svante

Répondre

18

Mise à jour:

La version précédente de ma réponse n'a pas été très robuste. Cela semble être une façon plus simple et plus approprié de le faire, volé 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}

#^{} et with-meta ne sont pas la même chose. Pour une explication de la différence entre eux, voir la discussion de Rich sur le Clojure mailing list. C'est un peu déroutant et il est apparu un tas de fois sur la liste de diffusion; voir aussi here par exemple.

Notez que def est une forme spéciale et gère les métadonnées un peu bizarrement par rapport à d'autres parties du langage. Il définit les métadonnées du var vous êtes def fing aux métadonnées du symbole qui nomme le var; C'est la seule raison pour laquelle ce qui précède fonctionne, je pense. Voir la classe DefExpr dans Compiler.java dans la source de Clojure si vous voulez voir le courage de tout cela.

Enfin, la page 216 du Programming Clojure dit:

Vous devez généralement éviter les macros de lecteur dans expansions macro, car les macros de lecture sont évaluées au moment de la lecture, avant le début de l'expansion macro.

+0

Intéressant! Mais y a-t-il un moyen de le faire plus fonctionnellement? Envelopper un «defn» dans un «do» puis modifier de manière destructive ses métadonnées me semble un peu bizarre. Là encore, mes expériences avec l'utilisation de la syntaxe '#^{: k: v}' depuis un 'defmacro' ont toutes été des échecs jusqu'à présent ... –

+0

Faire n'importe quel type de' def' n'est pas une chose très fonctionnelle faire, car il mute de manière destructive l'état d'une table de répartition globale. :) Mais vous avez raison, et j'ai mis à jour ma réponse. Ma réponse précédente était de laisser tomber les métadonnées #^Integer tag qu'un 'def' prendrait normalement. –

+0

Merci, c'est beaucoup plus compréhensible et ces liens semblent très utiles, même si je suis encore un peu mystifié de savoir pourquoi (macroexpand-1 '(defn-plus foo [bar] (baz))) ne montre pas le avec -meta tag. –

Questions connexes