Как я могу смешать дополнительные аргументы ключевых слов с и оставшимся?
-
02-10-2019 - |
Вопрос
У меня есть макрос, который принимает тело:
(defmacro blah [& body] (dostuffwithbody))
Но я хотел бы также добавить в него необязательный аргумент ключевого слова, поэтому, когда его называют, может выглядеть как любой из них:
(blah :specialthingy 0 body morebody lotsofbody)
(blah body morebody lotsofboy)
Как я могу это сделать? Обратите внимание, что я использую Clojure 1.2, поэтому я также использую новый необязательный аргумент ключевого слова Destructuring Stuff. Я наивно пытался сделать это:
(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]]
Другие советы
Михал Марчик хорошо ответил на ваш вопрос, но если вы готовы принять (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))