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-only
non 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.
vedere le spiegazioni qui: https://groups.google.com/forum/?fromgroups#!topic/comp.lang.lisp/F4NVRlOvrX8 –