Frage Wie kann ich optionale Keyword-Argumente mit den & rest-Inhalten mischen?


Ich habe ein Makro, das einen Körper braucht:

(defmacro blah [& body] (dostuffwithbody))

Aber ich möchte auch ein optionales Schlüsselwort-Argument hinzufügen, so dass es beim Aufruf wie folgt aussehen könnte:

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

Wie kann ich das machen? Beachten Sie, dass ich Clojure 1.2 verwende, daher verwende ich auch das neue optionale Schlüsselwort-Argument Destrukturierungskram. Ich habe naiv versucht, das zu tun:

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

Aber das hat natürlich nicht gut geklappt. Wie kann ich dies oder etwas ähnliches erreichen?


7
2018-05-02 15:28


Ursprung


Antworten:


Etwas wie das Folgende vielleicht (siehe auch wie defn und einige andere Makros in clojure.core sind festgelegt):

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

Alternativ könnten Sie anspruchsvoller sein; Es könnte sich lohnen, wenn Sie zu einem bestimmten Zeitpunkt mehrere mögliche Optionen haben möchten:

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

9
2018-05-02 15:57



Michał Marczyk hat Ihre Frage gut beantwortet, aber wenn Sie bereit sind, (foo {} Rest der Argumente) zu akzeptieren, also eine Karte mit den optionalen Schlüsselwörtern als erstes Argument (in diesem Beispiel leer), dann wird dieser einfachere Code funktionieren fein:

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

Funktioniert gleich für defmacro. Der Vorteil besteht darin, dass Sie sich nicht an eine spezielle Syntax für optionale Schlüsselwortparameter erinnern müssen und Code implementieren müssen, um die spezielle Syntax zu handhaben (möglicherweise sind andere Personen, die Ihr Makro aufrufen, mehr daran gewöhnt, Schlüssel-Wert-Paare anzugeben eine Karte als außerhalb davon). Der Nachteil ist natürlich, dass Sie immer eine Karte bereitstellen müssen, auch wenn Sie keine Schlüsselwortargumente angeben möchten.

Eine kleine Verbesserung, wenn Sie diesen Nachteil loswerden wollen, ist zu überprüfen, ob Sie eine Map als erstes Element Ihrer Argumentliste zur Verfügung gestellt haben:

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

3
2018-05-02 15:57