Как я могу смешать необязательные аргументы с ключевыми словами и & rest? - PullRequest
7 голосов
/ 02 мая 2010

У меня есть макрос, который принимает тело:

(defmacro blah [& body] (dostuffwithbody))

Но я бы хотел добавить к нему необязательный аргумент с ключевым словом, чтобы при вызове он мог выглядеть так:

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

Как я могу это сделать? Обратите внимание, что я использую Clojure 1.2, поэтому я также использую новый необязательный материал для разрушения аргументов ключевых слов Я наивно пытался это сделать:

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

Но, очевидно, это не сработало. Как я могу сделать это или что-то подобное?

Ответы [ 2 ]

9 голосов
/ 02 мая 2010

Возможно, что-то вроде следующего (также посмотрите, как определены 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]]
3 голосов
/ 02 мая 2010

Михал Марчик ответил на ваш вопрос хорошо, но если вы готовы принять (foo {} rest of the 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))
...