2010-05-02 55 views
7

我有一個宏,需要一個身體:如何將可選的關鍵字參數與其餘的東西混合?

(defmacro blah [& body] (dostuffwithbody)) 

但我想一個可選關鍵字參數添加到它一樣,所以當把它稱爲可能看起來像以下任一:

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

我該怎麼做?請注意,我使用的是Clojure 1.2,所以我也使用新的可選關鍵字參數destructuring stuff。我天真地試圖做到這一點:

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

但顯然這並沒有奏效。我怎樣才能做到這一點或類似的東西?

回答

9

喜歡的東西或許下面(也看到defn和在clojure.core中定義了一些其他的宏):

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

或者你可能更復雜;如果您認爲某些時候您可能想要有多種可能的選項,則可能值得:

(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很好地回答了您的問題,但是如果您願意接受(foo {}其餘參數),那麼可選關鍵字作爲第一個參數的映射(在本例中爲空),那麼這個更多簡單的代碼將正常工作:

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

Works的defmacro相同。這樣做的好處是,你不必爲可選的關鍵字參數記住一個特殊的語法,並實現代碼來處理特殊的語法(也許其他人將調用你的宏更習慣於指定鍵值對比它外面的地圖)。當然,缺點是你總是需要提供一張地圖,即使你不想提供任何關鍵字參數。

當你想擺脫這種不利的,如果你提供的地圖作爲參數列表的第一個元素是檢查小的改進:

(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)) 
相關問題