¿Cómo puedo mezclar argumentos de palabra clave opcionales con el & descansar cosas?

StackOverflow https://stackoverflow.com/questions/2753937

  •  02-10-2019
  •  | 
  •  

Pregunta

Tengo una macro que toma un cuerpo:

(defmacro blah [& body] (dostuffwithbody))

Pero me gustaría añadir un argumento palabra clave opcional a él también, así que cuando se le podría parecerse a cualquiera de los siguientes:

(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 material desestructuración argumento de palabra clave opcional. Ingenuamente tratado de hacer esto:

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

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

¿Fue útil?

Solución

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

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

Otros consejos

Michal Marczyk respondió a su pregunta muy bien, pero si usted está dispuesto a aceptar (foo {} resto de los argumentos), por lo que un mapa con las palabras clave opcionales como el primer argumento (vacío en este ejemplo), entonces esto más sencillo código funcionará 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 tiene que proporcionar un mapa, incluso cuando no se desea proporcionar argumentos de palabras 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))
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top