2012-03-19 24 views
9

Tome este ejemplo (simplificado):¿Cómo evitar el macro anafórico en Clojure?

(defmacro make [v & body] 
    `(let [~'nv ~(some-calc v)] 
    ~(map #(if (= % :value) 'nv %) body))) 

En este momento el símbolo nv está codificada. ¿Hay alguna manera de gensym de alguna manera nv y aún poder usarlo en la función de mapa?

Por cierto, ¿se trata de una macro anafórica?

+2

No estoy seguro si puede llamarlo anafórico: para serlo, 'nv' debe referirse a alguna parte del formulario de macro invocación. Si factoriza 'some-calc' del macro cuerpo, entonces tendrá una invocación anafórica más apropiada:' (make (some-calc x) (do-something-to nv)) 'donde' nv' se refiere a ' (algo-calc x) '. En su caso, tendrá '(make x (do-some-thing-to nv))', pero luego 'nv' no se referirá directamente a nada en el formulario de macro invocación. – skuro

+0

@skuro: Ya veo, básicamente esto es lo peor de ambos mundos: introducir un nuevo símbolo que, sin embargo, permanecerá oculto hasta que alguien lo redefina accidentalmente. Hf rastrear ese error :) Afortunadamente, un 'gensym' explícito según la sugerencia de amalloy resuelve este problema. –

Respuesta

4

La respuesta está contenido en la pregunta: solo usa gensym como lo haría si Clojure no tuviera auto gensyms.

(defmacro make [v & body] 
    (let [value-sym (gensym)] 
    `(let [~value-sym ~(some-calc v)] 
     [email protected](replace {:value value-sym} body)))) 

Tenga en cuenta que no estoy seguro de si realmente desea ~ o [email protected] aquí - depende si body se supone que es una secuencia de expresiones para ejecutar en el let, o una secuencia de argumentos para una sola función llamada. Pero [email protected] sería mucho más intuitivo/normal, así que eso es lo que voy a adivinar.

Si esta macro es anafórica es un poco cuestionable: definitivamente introduciendo nv en el alcance de la llamada fue, pero básicamente no fue intencional, así que diría que no. En mi versión revisada, ya no estamos introduciendo nv ni nada parecido, pero estamos "mágicamente" reemplazando :value con v. Sin embargo, solo hacemos eso en el nivel más alto del cuerpo, así que no es como introducir un alcance real, diría que es más como hacer que el código del cliente se rompa inesperadamente en casos de esquina. Para obtener un ejemplo de cómo puede surgir este comportamiento engañoso, imagine que uno de los elementos de body es . No será reemplazado por la macro, y se expandirá a (inc :value), lo que nunca tendrá éxito. Por lo tanto, en su lugar, recomendaría una macro anafórica real , que presenta un alcance real para un símbolo.Algo así como

(defmacro make [v & body] 
    `(let [~'the-value ~(some-calc v)] 
    [email protected])) 

Y entonces la persona que llama simplemente puede usar the-value en su código, y se comporta como un auténtico, local regular de unión: la macro lo introduce por arte de magia, pero no tiene ningún otro trucos especiales .

2

En su ejemplo, tanto some-calc y map suceda durante macroexpansion, no en tiempo de ejecución, por lo tanto nv no tiene que ser let de todos modos. La macro en sí no está escrita correctamente, independientemente de lo que tenga que ver con la captura de símbolos.

+0

Como dije, este es un ejemplo simplificado; la macro realmente devuelve un 'fn' que se llamará con relativa frecuencia, de ahí el" almacenamiento en caché "en let. –

+0

Realmente no me compro esto, Alex - está bastante claro que su 'mapa' está destinado a suceder durante la macroexpansión, y es completamente plausible que' some-calc' también lo sea. Por ejemplo, quizás '(some-calc 'x)' returns '' (inc (doto x prn))' - perfectamente adecuado para usar en ese contexto, y debe ser 'let' para evitar repetir el efecto secundario de la impresión. – amalloy

+0

Ciertamente, llamar 'map' y' some-calc' durante la expansión puede ser correcto. Mi punto era que el 'let' of' nv' en realidad no hace nada, por lo que la macro en sí misma no tenía ningún sentido con respecto a la pregunta sobre 'nv'. –

3

No es en realidad una macro anafórica como yo lo entiendo.

Un equivalente anafórica tendría que dar una sintaxis como:

(make foo 1 2 3 4 it 6 7 it 8 9) 

es decir, el símbolo it se ha definido de manera que pueda ser utilizado en el interior del cuerpo de la macro manera.

No estoy seguro de si esto es precisamente lo que quiere porque no tengo suficiente contexto de cómo se va a utilizar esta macro, pero se puede aplicar lo anterior como:

(defmacro make [v & body] 
    `(let [~'it ~v] 
     (list [email protected]))) 

(make (* 10 10) 1 2 3 4 it 6 7 it 8 9) 
=> (1 2 3 4 100 6 7 100 8 9) 

Alternativamente , si usted no está realmente tratando de crear una nueva sintaxis y sólo quiere reemplazar :value de alguna colección, entonces usted realmente no necesita una macro: sería mejor utilizar sólo replace:

(replace {:value (* 10 10)} [1 2 :value 3 4]) 
=> [1 2 100 3 4] 
+0

Aunque estoy aceptando la respuesta de amalloy, esto fue muy útil, ¡gracias! –

2

Un enfoque es utilizar un enlace dinámico.

(declare ^:dynamic *nv*) 

(defmacro make [v & body] 
    `(binding [*nv* ~(some-calc v)] 
    ~(map #(if (= % :value) *nv* %) body))) 

En la práctica, las variables dinámicas chupan cuando su alcance es demasiado ancho (es más difícil de probar y depurar programas, etc.), pero en casos como este, donde el alcance se limita al contexto de llamada local, donde una anáfora es necesario, pueden ser bastante útiles.

Un aspecto interesante de este uso es que es un poco lo contrario de una expresión común de usar una macro para ocultar una vinculación dinámica (muchos en el estilo con- *). En este idioma (que, por lo que yo sé, no es tan común), el enlace se utiliza para exponer algo oculto por la macro.

+1

Sí, recientemente comencé (ab) usando enlaces dinámicos y todavía estoy explorando casos en los que podrían ser útiles. ¡Gracias por la idea! –

Cuestiones relacionadas