Macros 如何将可选关键字参数与&;其他的东西?

Macros 如何将可选关键字参数与&;其他的东西?,macros,clojure,destructuring,Macros,Clojure,Destructuring,我有一个取实体的宏: (defmacro blah [& body] (dostuffwithbody)) 但我还想给它添加一个可选的关键字参数,因此当调用它时,它可能看起来像以下任何一个: (blah :specialthingy 0 body morebody lotsofbody) (blah body morebody lotsofboy) 我该怎么做?注意,我使用的是Clojure1.2,所以我还使用了新的可选关键字参数destructuring。我天真地尝试这样做: (d

我有一个取实体的宏:

(defmacro blah [& body] (dostuffwithbody))
但我还想给它添加一个可选的关键字参数,因此当调用它时,它可能看起来像以下任何一个:

(blah :specialthingy 0 body morebody lotsofbody)
(blah body morebody lotsofboy)
我该怎么做?注意,我使用的是Clojure1.2,所以我还使用了新的可选关键字参数destructuring。我天真地尝试这样做:

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

但很明显,这并不奏效。如何实现这一点或类似的功能?

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

类似于以下内容(还可以参见
defn
clojure.core
中的一些其他宏是如何定义的):

或者你可以更老练一些;如果您认为您可能希望在某个时候拥有多个可能的选项,那么这可能是值得的:

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