2010-07-15 4 views
9

Ho un numero arbitrario di elenchi che desidero elaborare utilizzando la macro per. Voglio creare una funzione che superi un vettore come associazione poiché varia il numero di elenchi.Problema con il passaggio di un vettore come associazione alla macro per

Se codificare l'associazione, funziona come mi aspetto:

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

Quando cerco di creare un vettore separatamente e utilizzare questo come il legame mi ha colpito problemi. Qui ho creare manualmente il binding vettore:

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

questo sembra benissimo:

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

Tuttavia,

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

Non capisco il motivo per cui testvector non è considerato un vettore quando viene utilizzato come vincolante in. Afferrando le cannucce, metto il testvector tra parentesi quadre che mantiene felice la macro (vede un vettore) ma ora ho un vettore con un elemento (cioè un vettore all'interno di un vettore) e questo non funziona perché il legame deve essere coppie di nome e collezione.

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

Qualsiasi suggerimento su come passare dinamicamente un vettore come binding a per sarebbe apprezzato.

+1

(vec (elenco ...)) può essere più semplice scritto come (vettore ...). – kotarak

risposta

5

La chiave è che per è una macro. Al momento dell'espansione della macro, testvector è un simbolo. Valuterà un vettore al momento della valutazione, ma non è un vettore dal punto di vista della macro per.

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 

Se si seleziona la sorgente del per macro (in core.clj), vedrai che per utilizza un non quotate vettore? Chiamata, proprio come tst nell'esempio sopra.

+0

Mille grazie per l'eccellente spiegazione ed esempio. –

+0

Per quelli di noi che stanno ancora imparando Clojure, qual è il prossimo passo per farlo funzionare davvero? Ho provato (defmacro combo [v] '(per ~ v [AB])) e non funziona con lo stesso messaggio di errore su _for_ che richiede un vettore –

+1

@JonathanBenn Sì, anche se si avvolge _for_ in una macro, stesso è ancora una macro e si applicano le stesse limitazioni. Questa risposta non è una soluzione, solo una spiegazione del perché non funzionerà. Al momento mi sta sfuggendo una soluzione a tempo parziale intelligente, ma penso che potresti risolvere il problema del test dell'OP con una funzione ricorsiva. – Greg

0

Ecco un metodo di ultima istanza. Stai attento, ovunque vedi read-string che è il codice per Here Be Dragons! (A causa di rischi per la sicurezza, e la mancanza di garanzie di coerenza in fase di compilazione circa il comportamento del codice)

(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

Anche se non è una soluzione al tuo problema, si segnala che cosa si sta facendo può essere più facilmente realizzato con la mappa piuttosto che per es

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=> 

Un'altra tecnica utile per l'apprendimento e la sperimentazione è l'utilizzo di parole chiave piuttosto che di stringhe. Ciò può ridurre la digitazione, ovvero non è necessario inserire i valori tra virgolette e talvolta può aiutare a identificare gli errori più facilmente. Invece di (def list1 '("pink" "green")) puoi semplicemente fare (def list1' (: pink: green)). Ancora meglio, piuttosto che usare le liste, prova a utilizzare i vettori e quindi non devi citarlo (salvando un altro tasto).

0

È possibile provare a forzare la valutazione del vettore di associazione. Invece di provare a definire una macro che avvolgerà la macro for, includila in una funzione, ad es.

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

Poi si può effettivamente costruire un vettore di legame con alcuni vincoli aggiuntivi dal momento che tutti s-espressioni all'interno della necessità vettore vincolante per essere valide e contengono un verbo come primo 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)) 

E con il tuo esempio:

(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: dal for-fn è la funzione, è necessario citare l'espressione (str A "-" B) per evitare una valutazione precoce (prima A & B sono vincolati).

Problemi correlati