2010-05-02 6 views
7

Ho una macro che prende un corpo:Come posso mescolare gli argomenti delle parole chiave opzionali con il resto?

(defmacro blah [& body] (dostuffwithbody)) 

Ma vorrei aggiungere un argomento parola chiave opzionale ad esso pure, in modo che quando lo chiamò potrebbe assomigliare a uno di questi:

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

Come posso farlo? Nota che sto usando Clojure 1.2, quindi sto anche usando il nuovo argomento di parole chiave destrutturanti. Ho ingenuamente provato a fare questo:

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

Ma ovviamente non ha funzionato bene. Come posso realizzare questo o qualcosa di simile?

risposta

9

Qualcosa di simile a quanto segue forse (si veda anche quanto defn e alcune altre macro in clojure.core sono definiti):

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

in alternativa si potrebbe essere più sofisticati; potrebbe essere utile se pensi che potresti voler avere più opzioni possibili a un certo punto:

(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

Michał Marczyk ha risposto bene alla tua domanda, ma se sei disposto ad accettare (foo {} resto degli argomenti), quindi una mappa con le parole chiave facoltative come primo argomento (vuoto in questo esempio), quindi questo più semplice codice funziona bene:

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

funziona allo stesso modo per defmacro. Il vantaggio di questo è che non devi ricordare una sintassi speciale per i parametri delle parole chiave opzionali e implementare il codice per gestire la sintassi speciale (forse altre persone che chiameranno la tua macro saranno più abituate a specificare le coppie chiave-valore in una mappa che al di fuori di esso). Lo svantaggio è ovviamente che devi sempre fornire una mappa, anche quando non vuoi fornire argomenti di parole chiave.

Un piccolo miglioramento quando si vuole sbarazzarsi di questo svantaggio è controllare se hai fornito una mappa come primo elemento della lista di argomenti:

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