我有一个宏观的宏:

(defmacro blah [& body] (dostuffwithbody))

但是我也想在其中添加一个可选的关键字参数,因此,当被称为时,它看起来都像是这样:

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

我怎样才能做到这一点?请注意,我正在使用Clojure 1.2,因此我还使用了破坏事物的新的可选关键字参数。我天真地尝试这样做:

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

但是显然,这并没有很好的效果。我该如何完成或类似的事情?

有帮助吗?

解决方案

也许像以下内容(也看看如何 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]]

其他提示

MichałMarczyk很好地回答了您的问题,但是如果您愿意接受(foo {}其余的Args),因此,将可选关键字作为第一个参数(在此示例中为空)的地图,那么此更简单的代码将有效美好的:

(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))
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top