2009-06-12 20 views
19

Mi rendo conto che la prima regola di Macro Club è Non usare macro, quindi la seguente domanda è intesa più come esercizio di apprendimento di Clojure che altro (Mi rendo conto che questo non è necessariamente il migliore utilizzare di macro).Aiutami a scrivere una macro Clojure che aggiunge automaticamente i metadati a una definizione di funzione

Voglio scrivere una macro semplice che funge da wrapper attorno a una macro regolare (defn) e termina aggiungendo alcuni metadati alla funzione definita. Così mi piacerebbe avere qualcosa di simile:

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

... espandere fuori a qualcosa di simile:

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

In linea di principio questo non sembra così difficile per me, ma io Ho problemi a individuare le specifiche per ottenere il [args] e altri moduli nella funzione definita da analizzare correttamente.

Come bonus, se possibile, vorrei che la macro fosse in grado di gestire tutte le diverse forme di defn (cioè, con o senza docstring, definizioni multiple di arità, ecc.). Ho visto alcune cose nel pacchetto clojure-contrib/def che sembrava possibilmente utile, ma è stato difficile trovare codice di esempio che le usasse.

+0

Buona introduzione ... oggetti di scena importanti per questo. – Kekoa

+0

Perché non utilizzare le macro? Stai pensando al preprocessore C? – Svante

risposta

18

Aggiornato:

La versione precedente della mia risposta non era molto robusto. Questo mi sembra un modo più semplice e più corretto di farlo, rubato da 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}

#^{} e with-meta non sono la stessa cosa. Per una spiegazione della differenza tra di loro, vedi la discussione di Rich sullo Clojure mailing list. È tutto un po 'confuso ed è venuto fuori un sacco di volte sulla mailing list; vedi anche here per esempio.

Si noti che def è un modulo speciale e gestisce i metadati un po 'stranamente rispetto ad altre parti della lingua. Imposta i metadati dello var sei def fing per i metadati del simbolo che dà il nome alla var; questa è l'unica ragione per cui funziona sopra, penso. Vedi la classe DefExpr in Compiler.java nella fonte Clojure se vuoi vedere il coraggio di tutto questo.

Infine, la pagina 216 del Programming Clojure dice:

Si dovrebbe evitare in generale le macro lettore in espansioni di macro, dal momento che le macro lettore vengono valutate in fase di lettura, prima dell'inizio macro espansione.

+0

Interessante! Ma c'è un modo per farlo più funzionalmente? Avvolgere un 'defn' in un' do' e quindi modificare i suoi metadati in modo distruttivo sembra un po 'strano per me. Poi di nuovo, i miei esperimenti con l'uso della sintassi '#^{: k: v}' all'interno di un 'defmacro' sono stati tutti fallimenti finora ... –

+0

Fare qualsiasi tipo di' def' non è una cosa molto funzionale a fa, dal momento che altera distruttivamente lo stato di una tabella di spedizione globale. :) Ma hai ragione, e ho aggiornato la mia risposta. La mia risposta precedente stava abbandonando i metadati del tag # Integer che normalmente un 'def' prenderebbe. –

+0

Grazie, questo è molto più comprensibile e quei link sembrano molto utili, anche se sono ancora un po 'sconcertato sul perché (macroexpand-1' (defn-plus foo [bar] (baz))) non mostra il -meta tag. –

Problemi correlati