Para dar una respuesta breve, las macros se utilizan para definir extensiones de sintaxis de lenguaje a Common Lisp o Lenguajes específicos de dominio (DSL). Estos idiomas están integrados directamente en el código Lisp existente. Ahora, los DSL pueden tener una sintaxis similar a Lisp (como el Prolog Interpreter de Peter Norvig para Common Lisp) o completamente diferente (por ejemplo, Infix Notation Math para Clojure).
Aquí hay un ejemplo más concreto:
Python tiene comprensiones de listas integradas en el lenguaje. Esto proporciona una sintaxis simple para un caso común. La línea
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
produce una lista que contiene todos los números pares entre 0 y 9. De vuelta en Python 1.5 días no había tal sintaxis; usarías algo más como esto:
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
Ambos son funcionalmente equivalentes. Invoquemos nuestra suspensión de la incredulidad y pretendamos que Lisp tiene una macro de bucle muy limitada que solo hace iteraciones y no es una manera fácil de hacer el equivalente de las comprensiones de listas.
En Lisp podrías escribir lo siguiente. Debo señalar que este ejemplo artificial se elige para ser idéntico al código de Python, no es un buen ejemplo de código Lisp.
;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
(if (= x 0)
(list x)
(cons x (range-helper (- x 1)))))
(defun range (x)
(reverse (range-helper (- x 1))))
;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)
;; loop from 0 upto and including 9
(loop for x in (range 10)
;; test for divisibility by two
if (= (mod x 2) 0)
;; append to the list
do (setq divisibleByTwo (append divisibleByTwo (list x))))
Antes de continuar, debería explicar mejor qué es una macro. Es una transformación realizada en código por código. Es decir, un fragmento de código, leído por el intérprete (o compilador), que toma el código como argumento, manipula y devuelve el resultado, que luego se ejecuta in situ.
Por supuesto, eso es mucho escribir y los programadores son flojos. Entonces podríamos definir DSL para hacer comprensiones de listas. De hecho, ya estamos usando una macro (la macro de bucle).
Lisp define un par de formas de sintaxis especiales. La cita ( '
) indica que el siguiente token es literal. El cuasiquote o backtick ( `
) indica que el siguiente token es un literal con escapes. Los escapes son indicados por el operador de coma. El literal '(1 2 3)
es el equivalente de Python [1, 2, 3]
. Puede asignarlo a otra variable o usarlo en su lugar. Puede pensar `(1 2 ,x)
como el equivalente de Python [1, 2, x]
donde x
es una variable previamente definida. Esta notación de la lista es parte de la magia que entra en las macros. La segunda parte es el lector Lisp que sustituye inteligentemente las macros por el código, pero que se ilustra mejor a continuación:
Entonces podemos definir una macro llamada lcomp
(abreviatura para la comprensión de la lista). Su sintaxis será exactamente como la pitón que usamos en el ejemplo [x for x in range(10) if x % 2 == 0]
:(lcomp x for x in (range 10) if (= (% x 2) 0))
(defmacro lcomp (expression for var in list conditional conditional-test)
;; create a unique variable name for the result
(let ((result (gensym)))
;; the arguments are really code so we can substitute them
;; store nil in the unique variable name generated above
`(let ((,result nil))
;; var is a variable name
;; list is the list literal we are suppose to iterate over
(loop for ,var in ,list
;; conditional is if or unless
;; conditional-test is (= (mod x 2) 0) in our examples
,conditional ,conditional-test
;; and this is the action from the earlier lisp example
;; result = result + [x] in python
do (setq ,result (append ,result (list ,expression))))
;; return the result
,result)))
Ahora podemos ejecutar en la línea de comando:
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
Bastante ordenado, ¿eh? Ahora no se detiene ahí. Tienes un mecanismo, o un pincel, si quieres. Puede tener cualquier sintaxis que pueda desear. Como Python o la with
sintaxis de C # . O la sintaxis LINQ de .NET. Al final, esto es lo que atrae a las personas a Lisp: la máxima flexibilidad.