Antigienico ("anaforico") macro Clojure per divertimento e profitto


Prima di entrare descrivere come ho trovato la mano anaforico macro, abbiamo bisogno di un po 'di storia. "Anaforico è una parola tratta da grammatici linguaggio naturale essenzialmente pronomi riferimento a "l'uso di un pronome o equivalente invece di ripetere una parola usata prima."

In linguaggi di programmazione, anaforica significa fondamentalmente la stessa cosa: per esempio, perl $ _ è un perfetto esempio di un pronome. Il manuale di Perl su questo tema chiamato $ _ un 'topicalizer "e fa l'esempio di inglesi" Carote, li odiano! "




Nelle lingue della famiglia lisp, anaforica a volte usare un po 'più in generale per indicare (all'incirca) ", che introduce una macro, o l'uso di simboli non lo fa esplicitamente" proprio "che possono conflitto con altri attacchi con lo stesso nome ".

Se avete esperienza con le macro creano Common Lisp, questa definizione può spaventare: sembra che cattura l'! Ed è vero, il simbolo di cattura involontaria è la fonte di alcuni errori molto sottili difficili, ed è un buon istinto di sopravvivenza a reagire con forza per catturare il simbolo. D'altra parte, rende molto difficile catturare simboli Clojure involontariamente (grazie!), E ben documentato simbolo cattura intenzionale può portare a qualche macro elegante e confortevole. Ecco un esempio di una classe in macro Clojure Paul Graham dice nel Lisp:

(Defmacro anarithmetic [& exprs] (Diminuzione (fn [resto expr] `(Let ~ ['in expr] Resto ~)) -È (Reverse exprs))) (Anarithmetic (10 + 20) (* 2 IT)) ;; espande a (Let [lei (10 + 20)] (Let [lei (* 2)] IT))

Questo è abbastanza semplice esempio, ma mostra come è possibile utilizzare una macro anaforico per esprimere lo stesso codice in modo diverso, introducendo il simbolo "che" nel codice di rappresentare implicitamente risultati intermedi. Il codice risultante è uguale a (2 * (10 + 20)). Si noti che la versione di Common Lisp ha bisogno di essere più attenti a non condividere le connessioni tra i diversi livelli di lasciare s ', perché è possibile utilizzare setf a un certo punto; I tipi di dati immutabile Clojure fanno di questo un argomento non vale la pena preoccuparsi.

Questo era solo un giocattolo

Nella vita reale non ho motivo di usare anarithmetic: Clojure di -> - >> e gli operatori per rendere ancora più facile da leggere, a mio parere. Ma di recente ho trovato diversi motivi per entrare nella cattura simbolo della mia macro per renderli più potenti. Il mio primo esempio potrebbe essere considerato "hacky" invece di "intelligente": Io sto bene con quello, perché riduce il numero di idee ancora ripetuti nel mio codice, e non esporrò una API pubblica.

Ecco il problema che volevo risolvere: Volevo scrivere alcuni defmethods che erano simili, e non c'è un modo semplice per farlo senza scrivere una macro che si espande a una o più defmethods, e poi chiamare. Si consideri la macro-make questo esempio utilizza la mia utility modello definisce una chiamata di macro, e quindi chiamare immediatamente su tutti gli argomenti previsti. Se si hanno difficoltà a leggere, andare a vedere la fonte della macro-do.

code espansione bersaglio

(Marchi Defmethod clojure.lang.IPersistentMap [Orig Swap] (Diminuzione (fn [acc [a b]] (Associazione acc uno (orig b) b (original))) orig swaps)) (Defmethod scambi clojure.lang.IPersistentVector [Orig Swap] (Diminuzione (fn [acc [a b]] (B Assoc Acc (orig a))) orig swaps))

Ci sono molto codice duplicato è, infatti, le uniche differenze sono nella classe di defmethod, e il corpo della funzione per ridurre. Ma se volessi scrivere un macro puramente igienico generalize ripetitivo questo, avrebbe dovuto prendere un, b e acc come argomenti, che i rifiuti scrivere molto. Davvero il modo "pulito" per farlo sarebbe quello di avere tutto il necessario (fn [...] ...) Parte come un argomento, ma che è la metà del boilerplate!

Al contrario, solo ho il mio macro di entrare a, b, e secondo come legature in espansione, e unisco l'espressione come parte della funzione di riduzione. Se qualcuno vuole usare nomi diversi per le loro variabili duri. Sto scrivendo questa macro per semplificare la scrittura di diversi metodi che tutti sostanzialmente ridotti in coppie di un vettore; quando voglio fare qualcosa che non a due a due, mi limiterò a scrivere defmethods mano.

Making it happen

(Macro-do [Class come] `(Scambi di classe Defmethod ~ [orig Exchange # ~ '] (Diminuzione (fn ~ '[acc [a b]] ~ Come) ~ 'Scambi Orig #)) clojure.lang.IPersistentMap (Associazione acc uno (orig b) b (orig a)) clojure.lang.IPersistentVector (B Assoc Acc (orig a)))

Perché so esattamente cosa espressioni contesto si innestano in, sono in grado di risparmiare un sacco di codifica tale in ogni defmethod macro "paga" anche con solo due usi.

Cosa succede a un esempio meno grave?

Va bene, gli attacchi introduzione di ultimo esempio certamente ridotto il codice, ma non esattamente come documentato a, b, e si comportano acc. Se qualcuno non vuole che io usi questa macro (che non può - ho fatto anonimo per salvarli da loro stessi) avrebbe dovuto fare qualcosa di 'studio per scoprire che cosa tutte le associazioni "media".

Anche l'esempio di On Lisp ", che" si sente un po '"innaturale: nella vita reale, la macro potrebbe essere ben documentato, ma con esso nel codice è destinato a sentirsi un po '"imbarazzante ancora. C'è un esempio reale mondo che si sente elegante, piuttosto che male?

A digressione: lazy-ss

Clojure ama sequenze pigri. Tutto è vago: mappa, ha ribadito, il filtro, concat - tutte queste producono sequenze che non hanno il calcolo fino a quando il chiamante richiede un elemento di sequenza. Lo fanno con la magia del lazy-ss macro, ma c'è funzioni esecutive così pigro disponibili quando necessario per produrre sequenze di pigrizia, di solito può fare combinando funzionalità esistenti, invece di utilizzare lazy-ss voi.

Tuttavia, come ho scoperto di recente, è sbagliato è spesso più facile e meno propensi a utilizzare lazy-ss, tuttavia, quando esistente offre un sacco di colla per entrare nella forma desiderata richiesta. Così per due volte di recente ho scritto un codice che assomiglia a questo:

Una macro anaforico: ciclo pigro

(Defn spiegare [? Seed fatto next] ((Fn sviluppo * [semi] (Lazy-ss (Sì-no (? Seed fatto) (Let [[nuovo valore di inizializzazione] (accanto al seme)] (Cons Valore (* Implementare nuovi semi)))))) le sementi))

A parte: alcuni preferiscono usare letfn per dichiarare funzione locale, e poi li chiamano per nome; Preferisco citazione solo gli effetti di auto-riferimento, e io ti chiamo quando mi chiamo. Questo è ciò che il mio ((fn citarne [arg] ...) real-arg) fa la sintassi.

In ogni caso, dichiarare la funzione interna, avvolgendo lazy-ss, e quindi chiamare questa funzione con gli argomenti iniziali è un po 'ripetitivo. È possibile automatizzare con una macro? Certo, ma perché dovrei dare un nome alla funzione interna? Sarebbe molto meglio se definito con la magia anaforica:

(Defn spiegare [? Seed fatto next] (Lazy-ciclo [Semi] (Sì-no (? Seed fatto) (Let [[nuovo valore di inizializzazione] (accanto al seme)] (Cons Valore (Pigro ripetuto nuovi semi-))))))

Questo è a soli due brevi righe, ma le linee sono molto più facili da leggere. ripetizione-loop lazy-pigro e si comportano come il tie-built/ripresentarsi, in modo che il lettore coglie immediatamente ciò che stanno facendo; con il mio codice originale, è necessario capire che cosa avrà luogo * 's svolge in tutto questo è, e seme tema inchiodato sembra fuori luogo: non è subito evidente che questo è passato come argomento a prendere posto *. Con lazy-lazy-loop e flussi di controllo naturali dall'alto verso il basso funzione si ripetono, senza la necessità di capire cosa seme appartiene: è chiaramente il valore iniziale del ciclo di rilegatura pigro come in (loop [Semi] ...).

Così come complicato è la macro che permette questo? E 'banale!

(Lazy-ciclo defmacro [Gli attacchi e corpo] (Let ['pigro recidivante-fn interna nomi (take-ennesimi 2 attacchi) valori (prendere-esimo 2 (attacchi di riposo))] `((Fn ~ interno-fn ~ (Vec Names) (Lazy-ss ~ @ Corpo)) ~ @ Values)))

Sembra davvero che solo un modello per la versione originale, tranne che per una logica di sbucciare a parte cappio attacchi nei nomi e valori, la fn vuole i nomi sulla lista dei parametri e dei valori nella chiamata ad esso. Non sono molto contento del mio-take th ripetizione: questo codice è probabile vedere un certo miglioramento nel corso del tempo. Ma è ancora abbastanza facile da leggere, ed è evidente che l'introduzione di un simbolo chiamato "risorsa artificiale". Nella versione reale esplicitamente documentata questa (e in effetti, la documentazione è quasi quanto il codice), ma questo centro si concentra sul codice che rende possibile.

Vai avanti e fare

Sono decisamente contento che Clojure rende difficile introdurre la cattura accidentale simbolo, ma mi stato esplicitamente dimostrato di avere più benefici di quanto mi aspettassi. La prossima volta che si sta scrivendo codice ripetitivo, e inevitabilmente si deve essere diviso in una funzione macro o separato, chiedetevi se sarebbe più conveniente o più facile da leggere per avere la macro "magicamente" fare alcune associazioni anche per te. Se è così, fare un tentativo - si può trovare il comando capture non è poi così male, dopo tutto!

(0)
(0)

Commenti - 0

Non ci sono commenti

Aggiungi un commento

smile smile smile smile smile smile smile smile
smile smile smile smile smile smile smile smile
smile smile smile smile smile smile smile smile
smile smile smile smile
Caratteri rimanenti: 3000
captcha