2010-05-02 7 views
7

Mam makro, które pobiera ciała:Jak mogę połączyć opcjonalne argumenty ze słowem kluczowym z rzeczami & spoczynku?

(defmacro blah [& body] (dostuffwithbody)) 

Ale chciałbym dodać opcjonalny argument słowa kluczowego do niego, tak więc po nazwie mógłby wyglądać jeden z nich:

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

Jak mogę to zrobić? Zauważ, że używam Clojure 1.2, więc używam również opcjonalnego argumentu polegającego na destrukcji słów kluczowych. Naiwnie próbowałem to zrobić:

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

Ale oczywiście to nie zadziałało dobrze. Jak mogę to zrobić lub coś podobnego?

Odpowiedz

9

Coś jak następujące może (patrz również jak defn i niektóre inne makra w clojure.core są zdefiniowane):

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

Alternatywnie można być bardziej wyrafinowane; może się to opłacać, jeśli uważasz, że możesz chcieć mieć kilka możliwych opcji w pewnym momencie:

(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 ładnie odpowiedział na twoje pytanie, ale jeśli chcesz zaakceptować (foo {} resztę argumentów), a więc mapę z opcjonalnymi słowami kluczowymi jako pierwszym argumentem (puste w tym przykładzie), to bardziej prosty kod zadziała poprawnie:

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

Działa tak samo dla defmacro. Zaletą tego jest to, że nie musisz pamiętać specjalnej składni dla opcjonalnych parametrów słów kluczowych i zaimplementować kod do obsługi specjalnej składni (być może inne osoby, które będą nazywać Twoje makro, są bardziej używane do określania par klucz-wartość w mapa niż poza nią). Wadą jest oczywiście to, że zawsze musisz podać mapę, nawet jeśli nie chcesz podawać żadnych argumentów słów kluczowych.

Niewielka poprawa, jeśli chcesz pozbyć się tej wady jest sprawdzenie, czy podałeś mapę jako pierwszy element z listy argumentów:

(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)) 
Powiązane problemy