2012-03-19 1 views
9

Prendete questo esempio (semplificato):Come evitare la macro anaforica in Clojure?

(defmacro make [v & body] 
    `(let [~'nv ~(some-calc v)] 
    ~(map #(if (= % :value) 'nv %) body))) 

In questo momento il simbolo nv è hardcoded. C'è un modo per gensym nv in qualche modo ed essere ancora in grado di usarlo nella funzione mappa?

A proposito, si tratta di una macro anaforica?

+2

Non so se è possibile chiamarlo anaphoric: per essere così, 'nv' dovrebbe fare riferimento ad alcune parti del modulo di invocazione macro. Se si calcola 'some-calc' dal corpo della macro, si avrà una invocazione anaforica più appropriata:' (make (some-calc x) (do-something-to nv)) 'dove' nv' si riferisce a ' (some-calc x) '. Nel tuo caso avrai '(make x (do-some-thing-to-nv))', ma in questo caso 'nv' non si riferirebbe direttamente a qualcosa nel modulo di invocazione macro. – skuro

+0

@skuro: Vedo, quindi in pratica questo è il peggiore dei due mondi - introducendo un nuovo simbolo che resterà comunque nascosto finché qualcuno non lo ridefinirà accidentalmente. Hf che tiene traccia di questo errore :) Fortunatamente, un esplicito 'gensym' come da suggerimento di amalloy risolve questo problema. –

risposta

4

La risposta è contenuto nella domanda: basta usare gensym come faresti se Clojure non avesse auto-gensyms.

(defmacro make [v & body] 
    (let [value-sym (gensym)] 
    `(let [~value-sym ~(some-calc v)] 
     [email protected](replace {:value value-sym} body)))) 

Nota che io non sono sicuro se si vuole veramente ~ o [email protected] qui - dipende se body si suppone essere una sequenza di espressioni da eseguire nel let, oppure una sequenza di argomenti per una singola funzione chiamata. Ma [email protected] sarebbe molto più intuitivo/normale, quindi è quello che intendo indovinare.

Se questa macro è anaforica è un po 'discutibile: sicuramente l'introduzione di nv nello scope chiamante era, ma sostanzialmente non intenzionale, quindi direi di no. Nella mia versione rivista, non stiamo più introducendo nv o qualcosa del genere, ma stiamo "magicamente" sostituendo :value con v. Lo facciamo solo al livello più alto del corpo, quindi, non è come introdurre un vero ambito - direi che è più come far si che il codice del client si interrompa inaspettatamente nei casi d'angolo.

Per un esempio di come questo comportamento ingannevole può emergere, immagina che uno degli elementi di body sia (inc :value). Non verrà sostituito dalla macro e si espanderà a (inc :value), che non ha mai successo. Quindi preferirei una macro anaphorica reale , che introduce un vero ambito per un simbolo.Qualcosa di simile

(defmacro make [v & body] 
    `(let [~'the-value ~(some-calc v)] 
    [email protected])) 

E poi il chiamante può semplicemente usare the-value nel loro codice, e si comporta proprio come un vero, regolare locale vincolante: la macro introduce per magia, ma non hanno altri trucchi speciali .

2

Nel tuo esempio, sia some-calc e map verificano durante macroexpansion, non in fase di esecuzione, quindi nv non ha bisogno di essere let comunque. La stessa macro non è scritta correttamente, indipendentemente da qualsiasi cosa abbia a che fare con la cattura dei simboli.

+0

Come ho detto, questo è un esempio semplificato; la macro restituisce effettivamente un 'fn' che verrà chiamato relativamente spesso, da qui il" caching "nel let. –

+0

Non lo compro davvero, Alex - è abbastanza chiaro che la sua 'map' è destinata ad accadere durante l'espansione della macro, ed è del tutto plausibile che' some-calc' potrebbe essere anche. Ad esempio, forse '(some-calc 'x)' restituisce '' (inc (doto x prn))' - perfettamente adatto per l'uso in quel contesto, e deve essere 'let' per evitare di ripetere l'effetto collaterale della stampa. – amalloy

+0

Certamente chiamare 'map' e' some-calc' durante l'espansione potrebbe essere corretto. Il mio punto era che il 'let' di' nv' in realtà non fa nulla, quindi la macro stessa non ha alcun senso rispetto alla domanda su 'nv'. –

3

In realtà non è una macro anaforica come ho capito.

Un equivalente anaforica avrebbe darvi una sintassi simile:

(make foo 1 2 3 4 it 6 7 it 8 9) 

cioè il simbolo it è stato definito in modo che possa essere utilizzato all'interno del corpo della macro.

non sono sicuro esattamente se questo è ciò che si vuole, perché io non ho abbastanza contesto su come questa macro sta per essere utilizzato, ma si potrebbe implementare quanto sopra come:

(defmacro make [v & body] 
    `(let [~'it ~v] 
     (list [email protected]))) 

(make (* 10 10) 1 2 3 4 it 6 7 it 8 9) 
=> (1 2 3 4 100 6 7 100 8 9) 

alternativa , se non stai veramente cercando di creare nuova sintassi e vogliono solo per sostituire :value in qualche collezione, allora non ha realmente bisogno di una macro: sarebbe meglio usare solo replace:

(replace {:value (* 10 10)} [1 2 :value 3 4]) 
=> [1 2 100 3 4] 
+0

Anche se sto accettando la risposta di Amalloy, questo è stato molto utile, grazie! –

2

Un approccio consiste nell'utilizzare un legame dinamico.

(declare ^:dynamic *nv*) 

(defmacro make [v & body] 
    `(binding [*nv* ~(some-calc v)] 
    ~(map #(if (= % :value) *nv* %) body))) 

In pratica, le variabili dinamiche succhiare quando il loro campo di applicazione è troppo ampio (è più difficile da testare e debug dei programmi, ecc), ma in casi come questo in cui il campo di applicazione è limitata alle contesto di chiamata locale, dove un anaphor è necessario, possono essere abbastanza utili.

Un aspetto interessante di questo utilizzo è che è un po 'l'inverso di un linguaggio comune dell'utilizzo di una macro per nascondere un'associazione dinamica (molti in stile with- *). In questo idioma (che per quanto ne so non è tutto ciò che è comune) il legame è usato per esporre qualcosa nascosto dalla macro.

+1

Sì, recentemente ho iniziato (ab) utilizzando i binding dinamici e sto ancora esplorando casi in cui potrebbero essere utili. Grazie per l'idea! –