2010-05-02 6 views
7

J'ai une macro qui prend corps:Comment puis-je mélanger des arguments de mots-clés optionnels avec les trucs & restes?

(defmacro blah [& body] (dostuffwithbody)) 

Mais je voudrais ajouter un argument de mot-clé facultatif aussi bien, alors quand appelé pourrait ressembler à l'une de ces:

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

Comment puis-je faire cela? Notez que j'utilise Clojure 1.2, donc j'utilise aussi le nouvel argument optionnel de déstructuration des mots-clés. J'ai naïvement essayé de faire ceci:

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

Mais évidemment, cela n'a pas bien fonctionné. Comment puis-je accomplir ceci ou quelque chose de similaire?

Répondre

9

Quelque chose comme ce qui suit voir peut-être (aussi comment defn et d'autres macros dans clojure.core sont définies):

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

Sinon, vous pouvez être plus sophistiqués; Il pourrait être utile de penser à plusieurs options possibles à un moment donné:

(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 a répondu à votre question gentiment, mais si vous êtes prêt à accepter (foo {} reste des arguments), donc une carte avec les mots-clés optionnels comme premier argument (vide dans cet exemple), alors cela plus simple code fonctionne très bien:

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

fonctionne de la même pour defmacro. L'avantage est que vous n'avez pas besoin de vous souvenir d'une syntaxe spéciale pour les paramètres de mots-clés facultatifs et que vous implémentez du code pour gérer la syntaxe spéciale (peut-être que les autres personnes qui appellent votre macro une carte qu'en dehors de celle-ci). L'inconvénient est bien sûr que vous devez toujours fournir une carte, même si vous ne voulez pas fournir d'argument mot-clé.

Une petite amélioration lorsque vous voulez vous débarrasser de cet inconvénient est de vérifier si vous avez fourni une carte comme premier élément de votre liste d'arguments:

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