2012-02-23 20 views
9

En un nivel conceptual, una macro en LISP (y dialectos) toma un fragmento de código (como lista) y devuelve otro fragmento de código (nuevamente como lista).Diferencia entre usar la lista y volver atrás en macros

Basado en principio por encima de una macro sencilla podría ser:

(defmacro zz [a] (list print a)) 
;macroexpand says : (#<core$print [email protected]> "Hello") 

Pero en clojure esto también se puede escribir como:

(defmacro zz [a] `(print ~a)) 
;macroexpand says : (clojure.core/print "Hello") 

No estoy exactamente seguro acerca de la diferencia aquí y que debería ser la forma preferida El primero parece simple, ya que se supone que debemos devolver la lista y evitar el uso de caracteres extraños como la marca de retroceso.

Respuesta

8

nadie ha señalado esto sin embargo ... la diferencia entre los 2 macros es la siguiente: su segunda forma (con tilde)

(defmacro zz [a] `(print ~a)) 

es equivalente a:

(defmacro zz [a] (list 'print a)) 

que es diferente de su primer ejemplo:

(defmacro zz [a] (list print a)) 

Tenga en cuenta la comilla única que falta - es por eso que su macroexpand es diferente. Estoy de acuerdo con la publicación de otras personas: el uso de backquote es más convencional si su macro tiene una 'forma' bastante simple. Si tiene que hacer un código walking o una construcción dinámica (es decir, una macro compleja), a menudo usar listas y compilarlas es lo que se hace.

Espero que esta explicación tenga sentido.

+1

En realidad es equivalente a '(list \' print a) ', que es ligeramente diferente:' 'print' solo funcionará si la persona que llama tiene 'print' refiriéndose a' clojure.core/print' en lugar de a un local, o no vinculante en absoluto (digamos que lo excluyeron del ': refer-clojure' de su espacio de nombres). '\' print', por otro lado, se expande directamente a 'clojure.core/print', por lo que no es ambiguo y es correcto en cualquier contexto. – amalloy

+0

@amalloy Gracias por responder, basándome en su sugestión, traté de hacer un macroexpand (mediante baba) en '' '(print ~ a)' y volví: '(seq (concat (list 'print) (list a))) '- ah, pero estás diciendo que la forma pre-transformada es más similar a tener' '' print '¿no? –

+0

No puedo leer su respuesta muy bien porque SO está comiendo algunos de los caracteres retrospectivos, pero: Slime está ocultando los espacios de nombres (y macroexpand no es necesario). Si solo cita la expresión, al escribir ''\' (print ~ a) 'en la réplica, verá que es equivalente a' (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/print)) (clojure.core/list a))) '. El 'clojure.core/print' es la distinción importante que estaba haciendo. – amalloy

1

En mi experiencia son equivalentes. Aunque puede haber algunos casos extremos que no conozco.

ejemplo @islon 's equivalentemente puede escribirse como:

Para el contrario, el código anterior es equivalente se puede escribir así:

(defmacro unless [condition & body] 
    (list 'if (list 'not condition) 
      (list* 'do body))) 
5

Hay una diferencia de estilo entre ellos. Su ejemplo es muy simple, pero en macros más complejas la diferencia será mayor.

Por ejemplo, el menos macro como se define en "La alegría de Clojure" libro:

(defmacro unless [condition & body] 
    `(if (not ~condition) 
     (do [email protected]))) 

Del libro:

Sintaxis comilla permite lo siguiente si forma para actuar como tipo de plantilla para la expresión que cualquier uso de la macro se convierte cuando se expande.

Al crear una macro siempre elija el estilo más legible e idiomático.

Para contrastar, el código anterior es equivalente se puede escribir así:

(defmacro unless [condition & body] 
    (list 'if (list 'not condition) 
      (list* 'do body))) 
6

Construir listas explícitamente es "más simple", de alguna manera, porque hay pocos conceptos básicos que necesita saber: simplemente acepte una lista y cámbiela hasta que tenga una nueva lista. Backtick es un atajo conveniente para "moldear" trozos de código; es posible escribir cualquier macro sin él, pero para cualquier macro grande se vuelve muy desagradable rápidamente. Por ejemplo, considere dos formas de escribir let como una macro sobre fn:

(defmacro let [bindings & body] 
    (let [names (take-nth 2 bindings) 
     vals (take-nth 2 (rest bindings))] 
    `((fn [[email protected]] 
     (do [email protected])) 
     [email protected]))) 

(defmacro let [bindings & body] 
    (let [names (take-nth 2 bindings) 
     vals (take-nth 2 (rest bindings))] 
    (cons (list `fn (vec names) (cons `do body)) 
      vals))) 

En el primer caso, el uso de comillas invertidas hace que sea bastante claro que usted está escribiendo una función de los nombres que contienen el cuerpo y, a continuación, llamar con los valores: el código de macro tiene "forma" igual que el código de expansión, por lo que puede imaginarse cómo se verá.

En el segundo caso, con solo cons y list en todas partes, es un verdadero dolor de cabeza descubrir cómo se verá la expansión. Este no es siempre el caso, por supuesto: a veces puede ser más claro escribir algo sin un retroceso.

Otro punto muy importante fue hecho por Kyle Burton: print no es lo mismo que 'print! La expansión de macro debe contener el símboloprint, no su valor (que es una función). Incrustar objetos (como funciones) en el código es muy frágil y solo funciona por accidente. Así que asegúrese de que sus macros se amplíen al código que realmente podría haber escrito usted mismo, y deje que el sistema de evaluación haga el trabajo duro; podría escribir el símbolo print, pero no podría escribir un puntero al valor actual de la función print .

Cuestiones relacionadas