2012-02-14 26 views
23

Trong clojure, apply không thể áp dụng cho macro. Ví dụ: (apply and [true false]) đặt ra một ngoại lệ. Tôi đã suy nghĩ về cách giải quyết như sau:Trong clojure, làm cách nào để áp dụng macro vào danh sách?

(defmacro apply-macro[func args] `(~func [email protected])) 

Thoạt nhìn, nó dường như làm việc khá tốt:

(apply-macro and [true 5]); 5 
(apply-macro and [true 5 0]); 0 
(let [a 0] (apply-macro and [true a])); 0 

Nhưng, khi tôi được truyền cho nó một biến trỏ đến một vector, nó sụp đổ.

(let [a [true]] (apply-macro and a)); java.lang.IllegalArgumentException: 
    ;Don't know how to create ISeq from: clojure.lang.Symbol 

Thật đáng thất vọng !!!!

Bất kỳ ý tưởng nào về cách khắc phục apply-macro?

Trả lời

24

Vấn đề là a chỉ là biểu tượng tại thời gian biên dịch. Vì vậy, không có cách nào cho một macro biên dịch thời gian để xem những gì nó chứa và làm việc mở rộng cần thiết. Do đó, bạn cần mở rộng macro tại thời gian chạy bằng cách sử dụng eval.

Một cách để làm điều này chỉ là để quấn vĩ mô trong một chức năng mà các cuộc gọi eval, có thể được thực hiện với tiện dụng "functionize" macro này:

(defmacro functionize [macro] 
    `(fn [& args#] (eval (cons '~macro args#)))) 

(let [a [true]] (apply (functionize and) a)) 
=> true 

Nếu bạn thích, bạn cũng có thể xác định áp dụng -macro về functionize:

(defmacro apply-macro [macro args] 
    `(apply (functionize ~macro) ~args)) 

(let [a [true false]] (apply-macro and a)) 
=> false 

có nói tất cả điều này, tôi vẫn nghĩ rằng điều tốt nhất để làm là để tránh macro hoàn toàn khi họ không thực sự cần thiết: họ bổ sung thêm tính phức tạp và được dành riêng tốt nhất cho trường hợp khi bạn thực sự cần tạo mã thời gian biên dịch. Trong trường hợp này, bạn không: Alex Taggart's answer đưa ra một ví dụ tốt về cách đạt được mục tiêu tương tự mà không có bất kỳ macro nào, có lẽ phù hợp hơn trong hầu hết các trường hợp.

+0

@YehonathanSharvit: điểm là '(áp dụng vĩ mô và a) 'được mở rộng thành' (và ) 'tại ** thời gian đọc **, nhưng' a' chỉ được xác định tại ** thời gian ** đánh giá. Vì vậy, 'unquote-splicing' của' a' (tức là '~ @ a') không thành công, vì' a' chưa được xác định. – liwp

+0

Chắc chắn, 'a' là một vectơ tại thời gian chạy. Nhưng không sử dụng nếu bạn muốn xây dựng một biểu mẫu '(và ....)' với nội dung của 'a' tại thời gian biên dịch, vì trình biên dịch không thể thấy nội dung của' a'. Do đó bạn cần phải gọi trình biên dịch * một lần nữa * tại thời gian chạy một khi bạn biết những gì 'a' chứa, do đó sự cần thiết cho eval. Đó là một chút phức tạp nhưng hy vọng rằng logic có ý nghĩa ..... – mikera

+0

'Functionize' có vẻ tuyệt vời !!! Bạn có phải là tác giả? Có bất kỳ hạn chế nào không? – viebel

27

Bạn không.

Macro được mở rộng trong thời gian đánh giá/biên dịch, không phải lúc chạy, do đó thông tin duy nhất mà chúng có thể sử dụng là args được chuyển vào, nhưng không phải là điều mà args đánh giá ở thời gian chạy. Đó là lý do tại sao một vector theo nghĩa đen hoạt động, bởi vì vector theo nghĩa đen đó có tại thời gian biên dịch, nhưng a chỉ là một biểu tượng; nó sẽ chỉ đánh giá một vector khi chạy.

Để có and hành vi giống như đối với danh sách, hãy sử dụng (every? identity coll).

Để có or hành vi giống như đối với danh sách, hãy sử dụng (some identity coll).

1

Tất nhiên, câu trả lời đúng là không làm điều này. Nhưng, kể từ khi tôi không thể cưỡng lại một tốt hack:

(defmacro apply-macro 
    "Applies macro to the argument list formed by prepending intervening 
    arguments to args." 
    {:arglists '([macro args] 
       [macro x args] 
       [macro x y args] 
       [macro x y z args] 
       [macro a b c d & args])} 
    [macro & args+rest] 
    (let [args (butlast args+rest) 
     rest-args (eval (last args+rest))] 
    `(eval 
     (apply (deref (var ~macro)) 
       '(~macro [email protected] [email protected]) 
       nil 
       [email protected](map #(list 'quote %) args) 
       '~rest-args)))) 

Cách sử dụng:

hackery> (->> (range 5) rest rest rest rest) 
(4) 
hackery> (apply-macro ->> (range 5) (repeat 4 'rest)) 
(4) 

Trình độ chuyên môn:

  1. vĩ mô không nên được trích dẫn, và các đối số can thiệp được truyền unevaluated cho macro.Tuy nhiên, đối số "còn lại" được đánh giá là và phải được đánh giá thành danh sách các biểu tượng hoặc biểu mẫu, mỗi biểu tượng sẽ được chuyển sang giá trị không được đánh giá macro.
  2. Điều này sẽ không hoạt động với các macro sử dụng đối số &env.
0

Cách tiếp cận này không hoạt động nếu danh sách đối số có thể có chiều dài không giới hạn, nhưng nếu bạn chỉ cần áp dụng cho danh sách lên đến chiều dài n bạn có thể làm cho một chức năng bao bọc với n arities:

user> (defmacro foo [& rest] `(println [email protected])) 
#'user/foo 
user> (apply foo [1 2]) 
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'user/foo, compiling:(*cider-repl repo*:865:7) 
user> (defn foo-up-to-ten-args 
    ([a]     (foo a)) 
    ([a b]     (foo a b)) 
    ([a b c]    (foo a b c)) 
    ([a b c d]    (foo a b c d)) 
    ([a b c d e]   (foo a b c d e)) 
    ([a b c d e f]   (foo a b c d e f)) 
    ([a b c d e f g]  (foo a b c d e f g)) 
    ([a b c d e f g h]  (foo a b c d e f g h)) 
    ([a b c d e f g h i] (foo a b c d e f g h i)) 
    ([a b c d e f g h i j] (foo a b c d e f g h i j))) 
#'user/foo-up-to-ten-args 
user> (apply foo-up-to-ten-args [1 2]) 
1 2 
nil 
user> (apply foo-up-to-ten-args (range 0 10)) 
0 1 2 3 4 5 6 7 8 9 
nil 
user> (apply foo-up-to-ten-args (range 0 11)) 
ArityException Wrong number of args (11) passed to: user/foo-up-to-ten-args clojure.lang.AFn.throwArity (AFn.java:429) 

Trong trường hợp của tôi, điều này đã làm những gì tôi cần mà không cần eval.

0

Khi áp dụng args để một macro có điều kiện như or, case, cond & condp ví dụ. Bạn có thể sử dụng các hàm some, every & partition. Mệnh đề khác duy nhất là bỏ qua trong các ví dụ nhưng có thể được thêm vào khá dễ dàng

;apply to the 'or' macro 
(some identity [nil false 1 2 3]) 
=> 1 

;apply to the 'case' macro. 
(some 
    (fn [[case value]] 
    (and (= case 2) value)) 
    (partition 2 [1 "one" 2 "two" 3 "three"])) 
=> "two" 

;apply to the 'cond' macro 
(some 
    (fn [[case value]] 
    (and case value)) 
    (partition 2 [false "one" true "two" false "three" :else "four"])) 
=> "two" 

;apply to the 'condp' macro 
(let [[f v & args] [= 2 1 "one" 2 "two" 3 "three"]] 
    (some 
    (fn [[case value]] 
     (and (f case v) value)) 
    (partition 2 args))) 

every? có thể được sử dụng cho các and vĩ mô

;apply to the 'and' macro 
(every? identity [true true true]) 
=> true 
Các vấn đề liên quan