2010-07-15 28 views
9

我有一個任意數量的列表,我想用宏來處理。我想創建一個傳遞一個向量作爲綁定的函數,因爲列表數目有所不同。傳遞一個向量作爲綁定到宏的問題

如果我硬編碼的結合,它的工作原理如我所料:

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

當我嘗試單獨創建一個向量,並以此作爲該綁定我打的問題。在這裏,我手動創建綁定向量:

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

這似乎罰款:

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

然而,

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

使用時,我不明白爲什麼testvector不被視爲一個向量作爲綁定。抓住吸管,我把testvector放在方括號中,它保持了宏的快樂(它看到一個向量),但現在我有一個向量有一個元素(即向量內的向量),這不起作用,因爲綁定需要成對的名稱和集合。

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

任何關於如何動態地傳遞一個向量作爲綁定的建議將不勝感激。

+1

(vec(list ...))可以簡單寫成(vector ...)。 – kotarak 2010-07-15 11:59:40

回答

5

關鍵是對於是一個宏。在宏展開時,testvector是一個符號。它將在評估時評估爲矢量,但它不是宏的視角中的矢量。

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 

如果你檢查的爲源宏(在core.clj),你會看到使用一個不帶引號載體?呼叫,就像上面例子中的tst一樣。

+0

非常感謝您的出色解釋和示例。 – 2010-07-15 01:37:07

+0

對於我們這些仍在學習Clojure的人來說,下一步實際工作是什麼?我已經嘗試過(defmacro combo [v]'(for〜v [AB])),並且它不能與關於_for_需要一個向量的錯誤消息一起使用。 – 2014-11-24 14:15:39

+1

@JonathanBenn是的,即使您將_for_換成宏,本身仍然是一個宏觀並且適用相同的限制。這個答案不是解決方案,只是解釋爲什麼它不起作用。一個聰明的宏觀時間解決方案目前正在逃避我,但我認爲你可以通過遞歸函數解決OP的測試問題。 – Greg 2014-11-26 03:36:39

0

這是一種最後的手段。被警告,無論你看到read-string這是代碼這裏是龍!(由於安全隱患,並缺乏編譯時一致性保證你的代碼的行爲)

(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

雖然不是你的問題的解決方案,但應注意的是,你在做什麼可以更容易地實現與地圖,而不是例如

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

另一個有用的技術,當學習和實驗是使用關鍵字而不是字符串。這可以減少輸入,即不需要將值放在引號中,有時可以更容易地識別錯誤。而不是(def list1'(「pink」「green」))你可以做(​​def list1'(:pink:green))。甚至更好,而不是使用列表,嘗試使用矢量,然後你不必引用它(保存另一個擊鍵)。

0

您可以嘗試強制對綁定向量進行評估。不是試圖定義一個宏來包裝for宏,而是將其包裝在一個函數中,例如,

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

然後你就可以真正建立一個綁定向量與一些附加約束綁定向量需要裏面,因爲所有s表達式是有效的,幷包含一個動詞爲第一要素。

(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)) 

而且隨着你的榜樣:

(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") 

注:因爲for-fn是功能,你需要引用表達(str A "-" B)以防止早期評估(A & B被束縛之前)。