7

我有一个需要身体的宏:

(defmacro blah [& body] (dostuffwithbody))

但我也想为它添加一个可选的关键字参数,所以当它被调用时,它可能看起来像以下任何一种:

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

我怎样才能做到这一点?请注意,我使用的是 Clojure 1.2,因此我还使用了新的可选关键字参数解构内容。我天真地尝试这样做:

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

但显然这并没有奏效。我怎样才能完成这个或类似的事情?

4

2 回答 2

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]]
于 2010-05-02T15:57:38.300 回答
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))

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))
于 2010-05-02T15:57:17.990 回答