tutorial net descargar compiler compiladores common lisp common-lisp

net - ¿Qué hay de malo con la siguiente macro Common Lisp usando gensym?



lisp descargar (5)

Aprendiendo Common Lisp (usando GNU CLISP 2.43) ... entonces podría ser un error novato. Ejemplo es el ''imprimir números primos entre x y y''

(defun is-prime (n) (if (< n 2) (return-from is-prime NIL)) (do ((i 2 (1+ i))) ((= i n) T) (if (= (mod n i) 0) (return NIL)))) (defun next-prime-after (n) (do ((i (1+ n) (1+ i))) ((is-prime i) i))) (defmacro do-primes-v2 ((var start end) &body body) `(do ((,var (if (is-prime ,start) ,start (next-prime-after ,start)) (next-prime-after ,var))) ((> ,var ,end)) ,@body)) (defmacro do-primes-v3 ((var start end) &body body) (let ((loop-start (gensym)) (loop-end (gensym))) `(do ((,loop-start ,start) (,loop-end ,end) (,var (if (is-prime ,loop-start) ,loop-start (next-prime-after ,loop-start)) (next-prime-after ,var))) ((> ,var ,loop-end)) ,@body )))

do-primes-v2 funciona perfectamente.

[13]> (do-primes-v2 (p 10 25) (format t "~d " p)) 11 13 17 19 23

Luego intenté usar gensym para evitar nombrar conflictos en la expansión de macros: do-primes-v3. Sin embargo, estoy atascado con un

*** - EVAL: variable #:G3498 has no value

Intenté usar Macro-expand para ver si podía detectar el error, pero no puedo.

[16]> (macroexpand-1 `(do-primes-v3 (p 10 25) (format t "~d " p))) (DO ((#:G3502 10) (#:G3503 25) (P (IF (IS-PRIME #:G3502) #:G3502 (NEXT-PRIME-AFTER #:G3502)) (NEXT-PRIME-AFTER P))) ((> P #:G3503)) (FORMAT T "~d " P)) ;


En realidad, no necesitas el gensym para evitar la captura de variables, porque no introduces ninguna variable que sea "local para la macro". Al macroexpandir tus do-primes-v2 , verás que no se introduce ninguna variable que no existiera fuera de la macro.

Sin embargo, lo necesita para una cosa diferente: evitar la evaluación múltiple.

Si llamas a la macro así:

(do-primes-v2 (p (* x 2) (* y 3)) (format "~a~%" p))

se expande a

(do ((p (if (is-prime (* x 2)) (* x 2) (next-prime-after (* x 2)) (next-prime-after p))) ((> p (* y 3)) (format "~a~%" p))

En el mejor de los casos, esto es ineficiente, porque esas multiplicaciones se realizan varias veces. Sin embargo, si usa una función con efectos secundarios como entradas, como setf o incf , esto puede ser un gran problema.


Mueva el enlace de su inicio de bucle y extremo de bucle a un bloque LET que lo rodea o use DO *. La razón es que todas las variables de bucle en DO están vinculadas "en paralelo", por lo que para la primera vinculación, la variable de inicio de bucle (expandida) todavía no tiene una vinculación.


Sugiero evitar DO / DO * y macros por completo y en su lugar apostar por Series (una implementación de las cuales se puede encontrar en series.sourceforge.net ).

Si eso es demasiado complejo, considere la posibilidad de generar una lista de primos con recursión o un generador (para la generación bajo demanda).


Use DO* lugar de DO .

DO Inicializa los enlaces en un ámbito en el que aún no están visibles. DO* inicializa los enlaces en un ámbito donde son visibles.

En este caso particular, var necesita hacer referencia al otro loop-start enlace.


Sé que esto realmente no responde a tu pregunta, pero sí creo que es relevante. En mi experiencia, el tipo de macro que intentas escribir es muy común. Un problema que tengo con la forma en que se ha enfocado en el problema es que no maneja otro caso de uso común: la composición funcional.

No tengo tiempo para resaltar algunas de las dificultades con las que probablemente se encontrará al utilizar su macro. Sin embargo, destacaré que, si hubiera construido su iterador principal orientado a la composición funcional, su macro resulta extremadamente simple, evitando su pregunta en total.

Nota: he modificado ligeramente algunas de tus funciones.

(defun is-prime (n) (cond ((< n 2) nil) ((= n 2) t) ((evenp n) nil) (t (do ((i 2 (1+ i))) ((= i n) t) (when (or (= (mod n i) 0)) (return nil)))))) (defun next-prime (n) (do ((i n (1+ i))) ((is-prime i) i))) (defun prime-iterator (start-at) (let ((current start-at)) (lambda () (let ((next-prime (next-prime current))) (setf current (1+ next-prime)) next-prime)))) (defun map-primes/iterator (fn iterator end) (do ((i (funcall iterator) (funcall iterator))) ((>= i end) nil) (funcall fn i))) (defun map-primes (fn start end) (let ((iterator (prime-iterator start))) (map-primes/iterator fn iterator end))) (defmacro do-primes ((var start end) &body body) `(map-primes #''(lambda (,var) ,@body) ,start ,end))

Yo también te recomiendo que mires la Serie . El patrón del generador también es una ocurrencia muy común en los programas de ceceo. También es posible que desee mirar a Alejandría , en particular la función ALEXANDRIA: COMPONENTE para ver qué cosas interesantes puede hacer con la composición funcional.