2010-05-02 13 views
7

tengo una macro que toma un cuerpo:¿Cómo puedo mezclar argumentos de palabra clave opcionales con & cosas de descanso?

(defmacro blah [& body] (dostuffwithbody)) 

Pero me gustaría añadir un argumento palabra clave opcional a él también, así que cuando lo llamó podría parecerse a cualquiera de éstos:

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

¿Cómo puedo hacer eso? Tenga en cuenta que estoy usando Clojure 1.2, así que también estoy usando el nuevo argumento de palabra clave para desestructurar cosas. Intenté ingenuamente hacer esto:

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

Pero obviamente eso no funcionó bien. ¿Cómo puedo lograr esto o algo similar?

Respuesta

9

algo como lo siguiente tal vez (también ver cómo defn y algunas otras macros en clojure.core se definen):

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

Alternativamente, usted podría ser más sofisticado; podría ser útil si usted piensa que podría querer tener varias opciones posibles en algún momento:

(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 respondió su pregunta muy bien, pero si está dispuesto a aceptar (foo {} el resto de los argumentos), un mapa con las palabras clave opcionales como primer argumento (vacío en este ejemplo), entonces este más código simple funciona 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)) 

funciona igual para defmacro. La ventaja de esto es que usted no tiene que recordar una sintaxis especial para los parámetros opcionales de palabras clave, e implementar código para manejar la sintaxis especial (tal vez otras personas que van a llamar a la macro están más acostumbrados a la especificación de pares de valores clave en un mapa que fuera de él). La desventaja es, por supuesto, que siempre debe proporcionar un mapa, incluso cuando no desee proporcionar ningún argumento clave.

Una pequeña mejora cuando se quiere deshacerse de este inconveniente está comprobando si proporcionó un mapa como el primer elemento de la lista de argumentos:

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