¿usted está mirando esto:
(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)))))
no es tan complicado, pero tiene una comilla inversa anidada, y múltiples niveles que son similares entre sí, lo que lleva a la fácil confusión, incluso para experimentados Codificadores Lisp.
Esto es una macro que utilizan las macros para escribir sus expansiones: una macro que escribe partes de los cuerpos de las macros.
Hay un let
llano en el cuerpo de la macro sí mismo, entonces una vez-backquoted generó let
que vivirá dentro del cuerpo de la macro que usa once-only
. Por último, hay una doble backsitio let
que aparecerá en la macro expansión de esa macro, en el sitio del código donde el usuario utiliza la macro.
Las dos rondas de generación de gensyms son necesarias porque once-only
es una macro en sí misma, por lo que tiene que ser higiénico por sí mismo; por lo que genera un grupo de gensyms por sí mismo en el extremo let
. Pero también, el propósito de once-only
es simplificar la escritura de otra macro higiénica. Entonces genera gensyms para esa macro también.
En pocas palabras, once-only
necesita crear una macro-expansión que requiere algunas variables locales cuyos valores son gensyms. Esas variables locales se usarán para insertar los gensimos en otra macro expansión para que sea higiénica. Y esas variables locales tienen que ser higiénicas, ya que son una expansión macro, por lo que también son gensimos.
Si estás escribiendo una macro sencilla, que tienen las variables locales que mantienen gensyms, ej .:
;; silly example
(defmacro repeat-times (count-form &body forms)
(let ((counter-sym (gensym)))
`(loop for ,counter-sym below ,count-form do ,@forms)))
En el proceso de escribir la macro, que ha inventado un símbolo, counter-sym
. Esta variable se define a simple vista. Tú, el humano, lo has elegido de tal manera que no choca con nada en el ámbito léxico. El alcance léxico en cuestión es el de su macro. No tenemos que preocuparnos por counter-sym
capturando accidentalmente referencias dentro de count-form
o forms
porque forms
son solo datos que entran en un fragmento de código que terminará insertado en algún ámbito léxico remoto (el sitio donde se usa la macro). Tenemos que preocuparnos por no confundir counter-sym
con otra variable dentro de nuestra macro. Por ejemplo, no podemos darle a nuestra variable local el nombre count-form
. ¿Por qué? Porque ese nombre es uno de nuestros argumentos de función; lo seguiríamos, creando un error de programación.
Ahora, si desea que una macro lo ayude a escribir esa macro, la máquina debe hacer el mismo trabajo que usted. Cuando está escribiendo código, tiene que inventar un nombre de variable, y debe tener cuidado con el nombre que inventa.
Sin embargo, la máquina de escribir códigos, a diferencia de usted, no ve el alcance circundante. No puede simplemente observar qué variables existen y elegir las que no chocan. La máquina es solo una función que toma algunos argumentos (piezas de código no evaluado) y produce una pieza de código que luego se sustituye ciegamente en un alcance después de que la máquina ha hecho su trabajo.
Por lo tanto, la máquina tiene que elegir los nombres extra sabiamente. De hecho, para ser completamente a prueba de balas, tiene que ser paranoico y usar símbolos que son completamente únicos: gensimos.
Continuando con el ejemplo, supongamos que tenemos un robot que escribirá este macro cuerpo para nosotros. Ese robot puede ser una macro, repeat-times-writing-robot
:
(defmacro repeat-times (count-form &body forms)
(repeat-times-writing-robot count-form forms)) ;; macro call
¿Cómo sería el robot macro parece?
(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))))
Se puede ver cómo esto tiene algunas de las características de once-only
: el doble de anidación y los dos niveles de (gensym)
. Si puede entender esto, entonces el salto a once-only
es pequeño.
Por supuesto, si solo quisiéramos que un robot escribiera tiempos de repetición, lo convertiríamos en una función, y entonces esa función no tendría que preocuparse por la invención de variables: no es una macro y por lo tanto no funciona t necesidad de la higiene:
;; 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
Pero once-only
no puede ser una función porque su trabajo es inventar las variables en nombre de su jefe, la macro que lo utiliza, y una función no se puede introducir variables en su llamador.
vea las explicaciones aquí: https://groups.google.com/forum/?fromgroups#!topic/comp.lang.lisp/F4NVRlOvrX8 –