2012-03-21 17 views
16

Nel libro "Practical Common Lisp" di Peter Seibel, possiamo trovare la definizione della macro molto complicata una sola volta (vedere la parte inferiore della pagina http://www.gigamonkeys.com/book/macros-defining-your-own.html).Comprendere come implementare una macro lisp una sola volta

Sto leggendo questa definizione macro per la decima volta nelle ultime 3 settimane e non riesco a capire come funziona. :(Peggio ancora, non riesco a sviluppare questa macro da solo, anche se capisco il suo scopo e come usarlo

Sono particolarmente interessato alla "derivazione" sistematica di questa macro notoriamente difficile, passo dopo passo! ? aiutare

+2

vedere le spiegazioni qui: https://groups.google.com/forum/?fromgroups#!topic/comp.lang.lisp/F4NVRlOvrX8 –

risposta

24

stai guardando questo:

(defmacro once-only ((&rest names) &body body) 
    (let ((gensyms (loop for n in names collect (gensym)))) 
    `(let (,@(loop for g in gensyms collect `(,g (gensym)))) 
     `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) 
     ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) 
      ,@body))))) 

non è così complicato, ma ha un backquote nidificate, e più livelli che sono simili tra loro, che porta alla facile confusione, anche per esperti Codificatori Lisp

Questo è una macro che viene usata dalle macro per scrivere le loro espansioni: una macro che scrive parti dei corpi delle macro.

C'è un semplice let nel corpo della macro stessa, quindi un backquote generato una volta let che risiederà nel corpo della macro che utilizza once-only. Infine, vi è un duplicato doppiamente let che verrà visualizzato nell'espansione macro di nella macro, nel sito del codice in cui la macro viene utilizzata dall'utente.

I due turni di generazione di gensyms sono necessari perché once-only è una macro stessa, e quindi deve essere igienico fine a se stesso; quindi genera un sacco di gensyms per se stesso nel più esterno let. Ma anche, lo scopo di once-only è di semplificare la scrittura di un'altra macro igienica. Quindi genera gensyms anche per quella macro.

In breve, once-only deve creare una macroespansione che richiede alcune variabili locali i cui valori sono gensyms. Queste variabili locali verranno utilizzate per inserire le gensyms in un'altra espansione macro per renderla igienica. E quelle variabili locali devono essere igieniche perché sono una macro espansione, quindi sono anche gensyms.

Se si scrive una macro pianura, avete le variabili locali che tengono gensyms, ad es .:

;; silly example 
(defmacro repeat-times (count-form &body forms) 
    (let ((counter-sym (gensym))) 
    `(loop for ,counter-sym below ,count-form do ,@forms))) 

Nel processo di scrittura della macro, si è inventato un simbolo, counter-sym. Questa variabile è definita in bella vista. Tu, l'umano, l'hai scelto in modo tale da non scontrarsi con nulla nella portata lessicale. Lo scope lessicale in questione è quello della tua macro. Non dobbiamo preoccuparci di acquisire accidentalmente i riferimenti all'interno di count-form o forms perché forms sono solo dati che stanno entrando in un pezzo di codice che verrà inserito in un ambito lessicale remoto (il sito in cui viene utilizzata la macro). Dobbiamo preoccuparci di non confondere counter-sym con un'altra variabile all'interno della nostra macro. Ad esempio, non possiamo dare alla nostra variabile locale il nome count-form. Perché? Perché quel nome è uno dei nostri argomenti di funzione; lo ombreggiamo creando un errore di programmazione.

Ora se si desidera che una macro aiuti a scrivere quella macro, la macchina deve fare lo stesso lavoro di voi. Quando scrive codice, deve inventare un nome variabile e deve fare attenzione a quale nome inventa.

Tuttavia, la macchina per scrivere il codice, a differenza di te, non vede l'ambito circostante. Non può semplicemente guardare quali variabili ci sono e scegliere quelle che non entrano in conflitto. La macchina è solo una funzione che prende alcuni argomenti (pezzi di codice non valutato) e produce un pezzo di codice che viene poi sostituito ciecamente in un ambito dopo che la macchina ha fatto il suo lavoro.

Pertanto, la macchina deve scegliere i nomi in modo saggia. In effetti, per essere completamente a prova di proiettile, deve essere paranoico e usare simboli che sono completamente unici: gensyms.

Quindi continuando con l'esempio, supponiamo di avere un robot che scriverà questo corpo macro per noi. Questo robot può essere una macro, repeat-times-writing-robot:

(defmacro repeat-times (count-form &body forms) 
    (repeat-times-writing-robot count-form forms)) ;; macro call 

quello che potrebbe il robot macro assomigliare?

(defmacro repeat-times-writing-robot (count-form forms) 
    (let ((counter-sym-sym (gensym)))  ;; robot's gensym 
    `(let ((,counter-sym-sym (gensym))) ;; the ultimate gensym for the loop 
     `(loop for ,,counter-sym-sym below ,,count-form do ,@,forms)))) 

Si può vedere come questo ha alcune delle caratteristiche della once-only: il doppio di nidificazione e le due livelli di (gensym). Se riesci a capire questo, allora il passaggio a once-only è piccolo.

Ovviamente, se volessimo che un robot scrivesse ripetizioni, lo renderemmo una funzione, e quindi quella funzione non dovrebbe preoccuparsi di inventare variabili: non è una macro e quindi non lo fa t bisogno di igiene:

;; i.e. regular code refactoring: a piece of code is moved into a helper function 
(defun repeat-times-writing-robot (count-form forms) 
    (let ((counter-sym (gensym))) 
    `(loop for ,counter-sym below ,count-form do ,@forms))) 

;; ... and then called: 
(defmacro repeat-times (count-form &body forms) 
    (repeat-times-writing-robot count-form forms)) ;; just a function now 

Ma once-onlynon può essere una funzione, perché il suo lavoro è inventare variabili per conto del suo capo, la macro che lo usa, e una funzione non possono introdurre variabili nel suo chiamante.

+4

P.S. puoi ringraziare la notazione backquote per rendere fattibile questo genere di cose. Senza il backquote, 'once-only' sarebbe orrendo. Solo qualcuno con capacità "idiot savant" nella programmazione Lisp sarebbe in grado di vedere attraverso di esso. La notazione backquote è il vero cavallo di battaglia nella scrittura macro Lisp. – Kaz

+2

* La sintassi di backquote era particolarmente potente quando nidificata. Ciò si è verificato principalmente all'interno delle macro di macro-definizione ; poiché tali codici venivano in primo luogo codificati dalle procedure guidate, la capacità di scrivere e interpretare le espressioni di backquote nidificate era presto circondata da una certa mistica. Alan Bawden del MIT ha acquisito una particolare reputazione come backquote-meister nei primi giorni della Lisp Machine. * - "The Evolution of Lisp", Gabriel, Steele. – Kaz

+3

* Backquote e DEFMACRO hanno fatto una grande differenza. Questo salto di forza espressiva, reso disponibile in formato standard , ha iniziato una nuova ondata di estensione del linguaggio, perché ora era molto più semplice definire nuovi costrutti di linguaggio in un modo standard, portatile, in modo che i dialetti sperimentali potessero essere condivisi . * " L'evoluzione della Lisp ", Gabriel, Steele. – Kaz

7

Un'alternativa alla macro once-only da Common Lisp pratico è derivata in Let Over Lambda (vedere la sezione "Una sola volta" nel terzo capitolo).

2

Kaz ha spiegato splendidamente e ampiamente.

Tuttavia, se non te ne importa molto circa il problema della doppia-igiene, si potrebbe trovare questo più facile da capire:

(defmacro once-only ((&rest symbols) &body body) 
    ;; copy-symbol may reuse the original symbol name 
    (let ((uninterned-symbols (mapcar 'copy-symbol symbols))) 
    ;; For the final macro expansion: 
    ;; Evaluate the forms in the original bound symbols into fresh bindings 
    ``(let (,,@(mapcar #'(lambda (uninterned-symbol symbol) 
          ``(,',uninterned-symbol ,,symbol)) 
         uninterned-symbols symbols)) 
     ;; For the macro that is using us: 
     ;; Bind the original symbols to the fresh symbols 
     ,(let (,@(mapcar #'(lambda (symbol uninterned-symbol) 
          `(,symbol ',uninterned-symbol)) 
         symbols uninterned-symbols)) 
      ,@body)))) 

Il primo let è backquoted due volte, perché sarà parte di l'espansione finale. Lo scopo è di valutare le forme nei simboli rilegati originali in associazioni nuove.

Il secondo let viene riarrangiato una volta, perché farà parte dell'utente di once-only. Lo scopo è di riassociare i simboli originali ai simboli freschi, poiché le loro forme saranno state valutate e vincolate a loro nell'espansione finale.

Se la riconnessione dei simboli originali era precedente all'espansione macro finale, l'espansione macro finale si riferiva ai simboli non integrati anziché alle forme originali.

Un'implementazione di with-slots che utilizza once-only è un esempio che richiede doppio igiene:

(defmacro with-slots ((&rest slots) obj &body body) 
    (once-only (obj) 
    `(symbol-macrolet (,@(mapcar #'(lambda (slot) 
            `(,slot (slot-value ,obj ',slot))) 
           slots)) 
     ,@body))) 

;;; Interaction in a REPL  
> (let ((*gensym-counter* 1) 
     (*print-circle* t) 
     (*print-level* 10)) 
    (pprint (macroexpand `(with-slots (a) (make-object-1) 
          ,(macroexpand `(with-slots (b) (make-object-2) 
              body)))))) 

;;; With the double-hygienic once-only 
(let ((#1=#:g2 (make-object-1))) 
    (symbol-macrolet ((a (slot-value #1# 'a))) 
    (let ((#2=#:g1 (make-object-2))) 
     (symbol-macrolet ((b (slot-value #2# 'b))) 
     body)))) 

;;; With this version of once-only 
(let ((#1=#:obj (make-object-1))) 
    (symbol-macrolet ((a (slot-value #1# 'a))) 
    (let ((#1# (make-object-2))) 
     (symbol-macrolet ((b (slot-value #1# 'b))) 
     body)))) 

La seconda espansione dimostra che il interno let è shadowing il legame alla variabile #:obj del outter let. Pertanto, l'accesso allo with-slots all'interno dello with-slots interno accederà effettivamente al secondo oggetto.

Si noti che in questo esempio, la macro-espansione esterna ottiene una gensym denominata g2 e la g1 interna. Nella normale valutazione o compilazione, sarebbe il contrario, poiché le forme vengono passate dall'outter all'intimo.

Problemi correlati