Comment créer une valeur par défaut pour l'argument de fonction dans Clojure


127

Je viens avec ceci:

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

(chaîne-> entier "10")
(chaîne-> entier "FF" 16)

Mais ce doit être une meilleure façon de le faire.

Réponses:


170

Une fonction peut avoir plusieurs signatures si les signatures diffèrent par leur arité. Vous pouvez l'utiliser pour fournir des valeurs par défaut.

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

Notez que les hypothèses falseet nilsont toutes deux considérées comme des non-valeurs, (if (nil? base) 10 base)peuvent être raccourcies à (if base base 10)ou plus loin (or base 10).


3
Je pense qu'il serait préférable que la deuxième ligne dise (recur s 10), en utilisant recurau lieu de répéter le nom de la fonction string->integer. Cela faciliterait le changement de nom de la fonction à l'avenir. Quelqu'un connaît-il une raison de ne pas utiliser recurdans ces situations?
Rory O'Kane

10
Il semble que cela recurne fonctionne que sur la même arité. si vous avez essayé de répéter ci-dessus, par exemple:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987

Ran dans ce même problème. Serait-il juste logique que la fonction s'appelle elle-même (ie (string->integer s 10))?
Kurt Mueller

155

Vous pouvez également déstructurer des restarguments sous forme de carte depuis Clojure 1.2 [ ref ]. Cela vous permet de nommer et de fournir des valeurs par défaut pour les arguments de fonction:

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

Maintenant tu peux appeler

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

ou

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

Vous pouvez le voir en action ici: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (par exemple)


1
Si facile à comprendre s'il vient d'un arrière-plan Python :)
Dan

1
C'est beaucoup plus facile à comprendre que la réponse acceptée ... est-ce la manière «clojurienne» acceptée ? Veuillez envisager d'ajouter à ce document.
Droogans

1
J'ai ajouté un problème au guide de style non officiel pour aider à résoudre ce problème.
Droogans

1
Cette réponse capture plus efficacement la manière «appropriée» de le faire que la réponse acceptée, bien que les deux fonctionnent correctement. (bien sûr, une grande puissance des langages Lisp est qu'il existe généralement de nombreuses façons différentes de faire la même chose fondamentale)
johnbakers

2
Cela me paraissait un peu verbeux et j'avais du mal à m'en souvenir pendant un moment, alors j'ai créé une macro un peu moins verbeuse .
akbiggs

33

Cette solution est la plus proche de l' esprit de la solution d'origine , mais légèrement plus propre

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

Un modèle similaire qui peut être des utilisations pratiques orcombiné aveclet

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

Bien que dans ce cas plus détaillé, cela peut être utile si vous souhaitez que les valeurs par défaut dépendent d'autres valeurs d'entrée . Par exemple, considérez la fonction suivante:

(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

Cette approche peut facilement être étendue pour fonctionner également avec des arguments nommés (comme dans la solution de M. Gilliar):

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

Ou en utilisant encore plus une fusion:

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

Si vous n'avez pas besoin de vos valeurs par défaut dépendant d'autres valeurs par défaut (ou peut-être même si vous le faites), la solution de Matthew ci-dessus permet également de multiples valeurs par défaut pour différentes variables. C'est beaucoup plus propre que d'utiliser un classiqueor
johnbakers

Je suis un noob de Clojure, alors peut-être qu'OpenLearner a raison, mais c'est une alternative intéressante à la solution de Matthew ci-dessus. Je suis heureux de savoir si je décide finalement de l'utiliser ou non.
GlenPeterson

orest différent de :orpuisque orne connaît pas la différence de nilet false.
Xiangru Lian

@XiangruLian Êtes-vous en train de dire que lorsque vous utilisez: ou, si vous passez false, il saura utiliser false au lieu de la valeur par défaut? Alors qu'avec ou il utiliserait la valeur par défaut lorsqu'il est passé false et non false lui-même?
Didier A.

8

Vous pouvez envisager une autre approche: les fonctions partielles . Il s'agit sans doute d'une manière plus «fonctionnelle» et plus flexible de spécifier les valeurs par défaut des fonctions.

Commencez par créer (si nécessaire) une fonction qui a le ou les paramètres que vous souhaitez fournir par défaut comme paramètre (s) principal:

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

Ceci est fait parce que la version de Clojure de partialvous permet de fournir les valeurs "par défaut" uniquement dans l'ordre dans lequel elles apparaissent dans la définition de la fonction. Une fois les paramètres classés comme vous le souhaitez, vous pouvez alors créer une version "par défaut" de la fonction à l'aide de la partialfonction:

(partial string->integer 10)

Afin de rendre cette fonction appelable plusieurs fois, vous pouvez la mettre dans un var en utilisant def:

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

Vous pouvez également créer une "valeur par défaut locale" en utilisant let:

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

L'approche de la fonction partielle a un avantage clé par rapport aux autres: le consommateur de la fonction peut toujours décider quelle sera la valeur par défaut plutôt que le producteur de la fonction sans avoir besoin de modifier la définition de la fonction . Ceci est illustré dans l'exemple hexoù j'ai décidé que la fonction par défaut decimaln'est pas ce que je veux.

Un autre avantage de cette approche est que vous pouvez attribuer à la fonction par défaut un nom différent (décimal, hexadécimal, etc.) qui peut être plus descriptif et / ou une portée différente (var, local). La fonction partielle peut également être mélangée avec certaines des approches ci-dessus si vous le souhaitez:

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

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

(Notez que ceci est légèrement différent de la réponse de Brian car l'ordre des paramètres a été inversé pour les raisons données en haut de cette réponse)


1
Ce n'est pas ce que demande la question; c'est intéressant cependant.
Zaz

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.