Cómo crear un valor predeterminado para el argumento de la función en Clojure

127

Vengo con esto:

(defn string-> integer [str & [base]]
  (Entero / parseInt str (if (nil? Base) 10 base)))

(cadena-> entero "10")
(cadena-> entero "FF" 16)

Pero debe ser una mejor manera de hacer esto.

jcubic
fuente

Respuestas:

170

Una función puede tener múltiples firmas si las firmas difieren en aridad. Puede usar eso para proporcionar valores predeterminados.

(defn string->integer 
  ([s] (string->integer s 10))
  ([s base] (Integer/parseInt s base)))

Nota que asumiendo falsey nilson a la vez los no valores considerados, (if (nil? base) 10 base)se podría acortar a (if base base 10), o adicionalmente a (or base 10).

Brian Carper
fuente
3
Creo que sería mejor que la segunda línea diga (recur s 10), usando en recurlugar de repetir el nombre de la función string->integer. Eso facilitaría cambiar el nombre de la función en el futuro. ¿Alguien sabe alguna razón para no usar recuren estas situaciones?
Rory O'Kane
10
Parece que recursolo funciona en el mismo arity. si se trató anteriormente se repiten, por ejemplo:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987
Me encontré con este mismo problema. ¿Tendría sentido tener que la función se llame a sí misma (es decir (string->integer s 10))?
Kurt Mueller
155

También puede desestructurar restargumentos como un mapa desde Clojure 1.2 [ ref ]. Esto le permite nombrar y proporcionar valores predeterminados para argumentos de función:

(defn string->integer [s & {:keys [base] :or {base 10}}]
    (Integer/parseInt s base))

Ahora puedes llamar

(string->integer "11")
=> 11

o

(string->integer "11" :base 8)
=> 9

Puede ver esto en acción aquí: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (por ejemplo)

Matthew Gilliard
fuente
1
Tan fácil de entender si proviene de un fondo de Python :)
Dan
1
Esto es mucho más fácil de entender que la respuesta aceptada ... ¿es esta la forma "Clojuriana" aceptada ? Por favor considere agregar a este documento.
Droogans
1
He agregado un problema a la guía de estilo no oficial para ayudar a abordar esto.
Droogans
1
Esta respuesta captura de manera más efectiva la forma "adecuada" de hacerlo que la respuesta aceptada, aunque ambas funcionarán bien. (por supuesto, un gran poder de los idiomas Lisp es que generalmente hay muchas formas diferentes de hacer lo mismo fundamental)
johnbakers
2
Esto me pareció un poco confuso y tuve problemas para recordarlo por un tiempo, así que creé una macro un poco menos detallada .
akbiggs
33

Esta solución es más cercana al espíritu de la solución original , pero marginalmente más limpia.

(defn string->integer [str & [base]]
  (Integer/parseInt str (or base 10)))

Un patrón similar que puede ser útil en orcombinación conlet

(defn string->integer [str & [base]]
  (let [base (or base 10)]
    (Integer/parseInt str base)))

Si bien en este caso es más detallado, puede ser útil si desea que los valores predeterminados dependan de otros valores de entrada . Por ejemplo, considere la siguiente función:

(defn exemplar [a & [b c]]
  (let [b (or b 5)
        c (or c (* 7 b))]
    ;; or whatever yer actual code might be...
    (println a b c)))

(exemplar 3) => 3 5 35

Este enfoque se puede extender fácilmente para trabajar con argumentos con nombre (como en la solución de M. Gilliar) también:

(defn exemplar [a & {:keys [b c]}]
  (let [b (or b 5)
        c (or c (* 7 b))]
    (println a b c)))

O usando aún más de una fusión:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoaroso
fuente
Si no necesita que sus valores predeterminados dependan de otros valores predeterminados (o tal vez incluso si lo hace), la solución de Matthew anterior también permite múltiples valores predeterminados para diferentes variables. Es mucho más limpio que usar un regularor
johnbakers
Soy un novato en Clojure, así que tal vez OpenLearner tenga razón, pero esta es una alternativa interesante a la solución de Matthew anterior. Me alegra saber sobre esto si finalmente decido usarlo o no.
GlenPeterson
ores diferente de :orya orque no conoce la diferencia de nily false.
Xiangru Lian
@XiangruLian ¿Está diciendo que cuando usa: o, si pasa falso, sabrá usar falso en lugar del predeterminado? Mientras que con o usaría el valor predeterminado cuando se pasa falso y no falso en sí mismo?
Didier A.
8

Hay otro enfoque que puede considerar: funciones parciales . Podría decirse que es una forma más "funcional" y más flexible de especificar valores predeterminados para funciones.

Comience creando (si es necesario) una función que tenga los parámetros que desea proporcionar como predeterminados como los parámetros principales:

(defn string->integer [base str]
  (Integer/parseInt str base))

Esto se hace porque la versión de Clojure le partialpermite proporcionar los valores "predeterminados" solo en el orden en que aparecen en la definición de la función. Una vez que los parámetros se han ordenado según lo deseado, puede crear una versión "predeterminada" de la función utilizando la partialfunción:

(partial string->integer 10)

Para hacer que esta función sea invocable varias veces, puede ponerla en una variable usando def:

(def decimal (partial string->integer 10))
(decimal "10")
;10

También puede crear un "valor predeterminado local" usando let:

(let [hex (partial string->integer 16)]
  (* (hex "FF") (hex "AA")))
;43350

El enfoque de función parcial tiene una ventaja clave sobre los demás: el consumidor de la función aún puede decidir cuál será el valor predeterminado en lugar del productor de la función sin necesidad de modificar la definición de la función . Esto se ilustra en el ejemplo con el hexque he decidido que la función predeterminada decimalno es lo que quiero.

Otra ventaja de este enfoque es que puede asignar a la función predeterminada un nombre diferente (decimal, hexadecimal, etc.) que puede ser más descriptivo y / o un alcance diferente (var, local). La función parcial también se puede mezclar con algunos de los enfoques anteriores si se desea:

(defn string->integer 
  ([s] (string->integer s 10))
  ([base s] (Integer/parseInt s base)))

(def hex (partial string->integer 16))

(Tenga en cuenta que esto es ligeramente diferente de la respuesta de Brian, ya que el orden de los parámetros se ha invertido por las razones que figuran en la parte superior de esta respuesta)

optevo
fuente
1
Esto no es lo que pide la pregunta; Sin embargo, es interesante.
Zaz