2010-05-02 6 views
7

Tôi có một vĩ mô mà phải mất một cơ thể:Làm cách nào để tôi có thể kết hợp các đối số từ khóa tùy chọn với công cụ & phần còn lại?

(defmacro blah [& body] (dostuffwithbody)) 

Nhưng tôi muốn thêm một đối số từ khóa tùy chọn để nó là tốt, vì vậy khi được gọi là nó có thể trông giống như một trong những:

(blah :specialthingy 0 body morebody lotsofbody) 
(blah body morebody lotsofboy) 

Tôi có thể làm như thế nào? Lưu ý rằng tôi đang sử dụng Clojure 1.2, vì vậy tôi cũng đang sử dụng công cụ hủy đối số từ khóa tùy chọn mới. Tôi ngây thơ đã cố gắng thực hiện điều này:

(defmacro blah [& {specialthingy :specialthingy} & body]) 

Nhưng rõ ràng điều đó không hoạt động tốt. Làm thế nào tôi có thể thực hiện điều này hoặc một cái gì đó tương tự?

Trả lời

9

Something như sau có lẽ (xem thêm cách defn và một số macro khác trong số clojure.core được xác định):

(defmacro blah [& maybe-option-and-body] 
    (let [has-option (= :specialthingy (first maybe-option-and-body)) 
     option-val (if has-option (second maybe-option-and-body)) ; nil otherwise 
     body (if has-option (nnext maybe-option-and-body) maybe-option-and-body)] 
    ...)) 

Hoặc bạn có thể tinh vi hơn; nó có thể đáng giá nếu bạn nghĩ bạn có thể muốn có nhiều tùy chọn có thể tại một số điểm:

(defn foo [& args] 
    (let [aps (partition-all 2 args) 
     [opts-and-vals ps] (split-with #(keyword? (first %)) aps) 
     options (into {} (map vec opts-and-vals)) 
     positionals (reduce into [] ps)] 
    [options positionals])) 

(foo :a 1 :b 2 3 4 5) 
; => [{:a 1, :b 2} [3 4 5]] 
3

Michał Marczyk trả lời câu hỏi của bạn một cách độc đáo, nhưng nếu bạn sẵn sàng chấp nhận (foo {} phần còn lại của arg), vì vậy một bản đồ với các từ khóa tùy chọn là đối số đầu tiên (trống trong ví dụ này) mã đơn giản sẽ hoạt động tốt:

(defn foo [{:keys [a b]} & rest] (list a b rest)) 
(foo {} 2 3) 
=> (nil nil (2 3)) 
(foo {:a 1} 2 3) 
=> (1 nil (2 3)) 

Hoạt động tương tự cho defmacro. Lợi thế của việc này là bạn không phải nhớ cú pháp đặc biệt cho các tham số từ khóa tùy chọn và triển khai mã để xử lý cú pháp đặc biệt (có thể những người khác gọi macro của bạn được sử dụng nhiều hơn để chỉ định cặp khóa-giá trị trong một bản đồ bên ngoài nó). Bất lợi là tất nhiên bạn luôn phải cung cấp một bản đồ, ngay cả khi bạn không muốn cung cấp bất kỳ đối số từ khóa nào.

Một cải tiến nhỏ khi bạn muốn thoát khỏi bất lợi này là kiểm tra nếu bạn cung cấp một bản đồ như các yếu tố đầu tiên của danh sách đối số của bạn:

(defn foo [& rest] 
    (letfn [(inner-foo [{:keys [a b]} & rest] (list a b rest))] 
    (if (map? (first rest)) 
     (apply inner-foo rest) 
     (apply inner-foo {} rest)))) 

(foo 1 2 3) 
=> (nil nil (1 2 3)) 
(foo {:a 2 :b 3} 4 5 6) 
=> (2 3 (4 5 6))