2010-07-15 6 views
9

Tengo un número arbitrario de listas que me gustaría procesar usando el macro for. Quiero crear una función que pase un vector como enlace ya que el número de listas varía.Problema al pasar un vector como un enlace al macro for

Si codificar la unión, funciona como espero:

=> (def list1 '("pink" "green")) 
=> (def list2 '("dog" "cat")) 
=> (for [A list1 B list2] (str A "-" B)) 
("pink-dog" "pink-cat" "green-dog" "green-cat") 

Cuando intento crear un vector separado y utilizar esto como la unión golpeo problemas. Aquí se crea manualmente el vector de fijaciones:

=> (def testvector (vec (list 'A list1 'B list2))) 

esto parece muy bien:

=> testvector 
[A ("pink" "green") B ("dog" "cat")] 
=> (class testvector) 
clojure.lang.PersistentVector 

Sin embargo,

=> (for testvector (str A "-" B)) 
#<CompilerException java.lang.IllegalArgumentException: for requires a vector for its binding (NO_SOURCE_FILE:36)> 

No entiendo por qué testvector no se considera un vector cuando se usan como el enlace para. Agarrando pajas, puse Testvector entre corchetes que mantiene feliz el macro (ve un vector) pero ahora tengo un vector con un elemento (es decir, un vector dentro de un vector) y esto no funciona porque el enlace necesita ser pares de nombre y colección.

=> (for [testvector] (str A "-" B)) 
#<CompilerException java.lang.IllegalArgumentException: for requires an even number of forms in binding vector (NO_SOURCE_FILE:37)> 

Se agradecerá cualquier sugerencia sobre cómo pasar dinámicamente un vector como un enlace a para.

+1

(vec (lista ...)) puede escribirse más simple como (vector ...). – kotarak

Respuesta

5

La clave es que para es una macro. En el momento de la macro expansión, testvector es un símbolo. Se evaluará a un vector en el momento de la evaluación, pero no es un vector desde la perspectiva de la macro para.

user=> (defmacro tst [v] (vector? v)) 
#'user/tst 
user=> (tst testvector) 
false 
user=> (vector? testvector) 
true 
user=> (defmacro tst2 [v] `(vector? ~v)) 
#'user/tst2 
user=> (tst2 testvector) 
true 

Si marca la fuente para el de macro (en core.clj), verá que para utiliza un vector de sin comillas? llamada, al igual que tst en el ejemplo anterior.

+0

Muchas gracias por la excelente explicación y ejemplo. –

+0

Para aquellos de nosotros que todavía estamos aprendiendo Clojure, ¿cuál es el siguiente paso para que realmente funcione? Lo he intentado (defmacro combo [v] '(para ~ v [AB])) y no funciona con el mismo mensaje de error sobre _para_ necesitar un vector –

+1

@JonathanBenn Sí, incluso si ajusta _for_ en una macro, en sí mismo sigue siendo una macro y se aplican las mismas limitaciones. Esta respuesta no es una solución, solo una explicación de por qué no funcionará. En este momento, se me está escapando una solución inteligente de macro-tiempo, pero creo que podría resolver el problema de prueba de OP con una función recursiva. – Greg

0

Este es un método de último recurso. Tenga cuidado, donde sea que vea read-string que es el código para ¡Aquí hay dragones! (Debido a los riesgos de seguridad y la falta de garantía de consistencia en tiempo de compilación sobre el comportamiento de su código)

(def list1 '("pink" "green")) 
(def list2 '("dog" "cat")) 
(for [A list1 B list2] (str A "-" B)) 

(def testvector (vec (list 'A list1 'B list2))) 

(def testvector-vec (vec (list 'A (vec list1) 'B (vec list2)))) 

(def for-string (str "(for " testvector-vec "(str A \"-\" B))")) 

(eval (read-string for-string)) 
> ("pink-dog" "pink-cat" "green-dog" "green-cat") 
0

Aunque no es una solución a su problema, debe tenerse en cuenta que lo que está haciendo puede ser más fácil logrado con el mapa en lugar de, por ejemplo,

user=> (def list1 '("pink" "green")) 
#'user/list1 
user=> (def list2 '("dog" "cat")) 
#'user/list2 
user=> (map #(str %1 "-" %2) list1 list2) 
("pink-dog" "green-cat") 
user=> 

Otra técnica útil al aprender y experimentar es utilizar palabras clave en lugar de cadenas. Esto puede reducir el tipeo, es decir, no es necesario poner los valores entre comillas y, a veces, puede ayudar a identificar los errores más fácilmente. En lugar de (def list1 '("rosa" "verde")) puede hacer (def list1' (: pink: green)). Aún mejor, en lugar de utilizar listas, intente utilizar vectores y luego no tiene que citarlo (guardando otra tecla).

0

Puede intentar forzar la evaluación del vector de enlace. En lugar de tratar de definir una macro que ajustará la macro for, envuélvala en una función, p.

(defn for-fn [bindings expr] 
    (eval `(for ~bindings ~expr))) 

A continuación, en realidad se puede construir un vector de unión con algunas restricciones adicionales ya que todo s-expresiones dentro de la necesidad de unión del vector a ser válidos y contienen un verbo como primer elemento.

(let [bindings '[a (list 1 2) b (list 3 4) c (range 10 12) 
       :when (> (+ a b c) 15)] 
     expr '(str a "-" b "-" c)] 
    (for-fn bindings expr)) 

Y con su ejemplo:

(def list1 '("pink" "green")) 
(def list2 '("dog" "cat")) 
(def testvector (vector 'A (cons 'list list1) 'B (cons 'list list2))) 

(for-fn testvector '(str A "-" B)) 
=> ("pink-dog" "pink-cat" "green-dog" "green-cat") 

Nota: desde for-fn es función, es necesario citar la expresión (str A "-" B) para impedir una evaluación temprana (antes están unidos un & B).

Cuestiones relacionadas