如何将可选的关键字参数与&REST的内容混合?
-
02-10-2019 - |
题
我有一个宏观的宏:
(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))
不隶属于 StackOverflow