2010-05-02 25 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)) 
相关问题