2010-07-15 18 views
9

Tôi có một số lượng danh sách tùy ý mà tôi muốn xử lý bằng cách sử dụng macro. Tôi muốn tạo một hàm truyền một vectơ làm liên kết vì số lượng danh sách thay đổi.Sự cố khi truyền vectơ như một ràng buộc cho macro

Nếu tôi cứng mã ràng buộc, nó hoạt động như tôi mong đợi:

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

Khi tôi cố gắng tạo ra một vector riêng biệt và sử dụng như các ràng buộc tôi nhấn vấn đề. Ở đây tôi tự tạo các ràng buộc vector:

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

này có vẻ tốt đẹp:

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

Tuy nhiên,

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

Tôi không hiểu tại sao testvector không được xem là một véc tơ khi sử dụng như ràng buộc trong. Nắm lấy ống hút, tôi đặt testvector trong các dấu ngoặc vuông, giữ cho macro hạnh phúc (nó nhìn thấy một vector) nhưng bây giờ tôi có một vector với một phần tử (ví dụ: vector trong vectơ) và điều này không hoạt động. là cặp tên và bộ sưu tập.

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

Bất kỳ đề xuất nào về cách tự động truyền véc tơ như một ràng buộc cho sẽ được đánh giá cao.

+1

(vec (danh sách ...)) có thể được viết đơn giản dưới dạng (vectơ ...). – kotarak

Trả lời

5

Điều quan trọng là cho là một macro. Tại thời gian mở rộng vĩ mô, testvector là một biểu tượng. Nó sẽ đánh giá một vectơ tại thời điểm đánh giá, nhưng nó không phải là một vectơ từ quan điểm của cho macro.

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 

Nếu bạn kiểm tra nguồn cho cho vĩ mô (trong core.clj), bạn sẽ thấy rằng cho sử dụng một thể viện chứng vector? Gọi, giống như tst trong ví dụ ở trên.

+0

Rất cám ơn vì lời giải thích và ví dụ tuyệt vời. –

+0

Đối với những người trong chúng ta vẫn đang học Clojure, bước tiếp theo để thực sự làm cho nó hoạt động là gì? Tôi đã thử (defmacro combo [v] '(cho ~ v [AB])) và nó không hoạt động với cùng một thông báo lỗi về _for_ cần một vector –

+1

@JonathanBenn Vâng, ngay cả khi bạn bọc _for_ trong macro, nó bản thân nó vẫn là một macro và các giới hạn tương tự cũng được áp dụng. Câu trả lời này không phải là một giải pháp, chỉ là một lời giải thích tại sao nó không hoạt động. Một giải pháp thông minh thời gian vĩ mô là thoát khỏi tôi vào lúc này, nhưng tôi nghĩ rằng bạn có thể giải quyết vấn đề kiểm tra của OP với một hàm đệ quy. – Greg

0

Đây là phương pháp cuối cùng. Được cảnh báo, bất cứ nơi nào bạn nhìn thấy read-string là mã cho Đây là Rồng! (Do nguy cơ bảo mật, và thiếu đảm bảo tính thống nhất thời gian biên dịch về hành vi của mã của bạn)

(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

Mặc dù không phải là một giải pháp cho vấn đề của bạn, cần lưu ý rằng những gì bạn đang làm có thể dễ dàng được đạt được với bản đồ thay vì ví dụ

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

Một kỹ thuật hữu ích khác khi học và thử nghiệm là sử dụng từ khóa thay vì chuỗi. Điều này có thể làm giảm việc nhập, nghĩa là không cần phải đặt các giá trị trong dấu ngoặc kép và đôi khi có thể giúp xác định lỗi dễ dàng hơn. Thay vì (def list1 '("pink" "green")) bạn chỉ có thể làm (def list1' (: pink: green)). Thậm chí tốt hơn, thay vì sử dụng danh sách, hãy thử sử dụng vectơ và sau đó bạn không phải trích dẫn nó (lưu một phím tắt khác).

0

Bạn có thể thử bắt buộc đánh giá vectơ ràng buộc. Thay vì cố gắng xác định macro sẽ bao trùm macro for, hãy bọc macro đó vào hàm, ví dụ:

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

Sau đó, bạn thực sự có thể xây dựng một véc tơ ràng buộc với một vài khó khăn thêm vì tất cả s-biểu thức bên trong cần vector ràng buộc mới có giá trị và chứa một động từ là yếu tố đầu tiên.

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

Và với ví dụ của bạn:

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

Lưu ý: kể từ for-fn là chức năng, bạn cần phải trích dẫn biểu (str A "-" B) để ngăn chặn việc đánh giá sớm (trước Một & B đang bị ràng buộc).

Các vấn đề liên quan