macros - planificador - ¿Cómo puedo mezclar argumentos de palabras clave opcionales con las cosas de & rest?
palabras claves ejemplos (2)
Algo como lo siguiente quizás (también vea cómo defn
y algunas otras macros en clojure.core
están definidas):
(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, podrías ser más sofisticado; podría valer la pena si cree que podría querer tener múltiples 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]]
Tengo una macro que tiene un cuerpo:
(defmacro blah [& body] (dostuffwithbody))
Pero también me gustaría agregar un argumento de palabra clave opcional, así que cuando se lo llame podría parecerse a cualquiera de estos:
(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?
Michał Marczyk respondió su pregunta muy bien, pero si está dispuesto a aceptar (foo {} el resto de los argumentos), entonces un mapa con las palabras clave opcionales como primer argumento (vacío en este ejemplo), entonces este código más simple funcionará multa:
(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 no tiene que recordar una sintaxis especial para los parámetros de palabra clave opcionales e implementar código para manejar la sintaxis especial (tal vez otras personas que llamarán a su macro están más acostumbradas a especificar pares clave-valor 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 desee deshacerse de esta desventaja es verificar si proporcionó un mapa como primer elemento de su 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))