Come creare un valore predefinito per l'argomento della funzione in Clojure


127

Vengo con questo:

(defn string-> intero [str & [base]]
  (Intero / parseInt str (if (zero? Base) 10 base)))

(stringa-> numero intero "10")
(stringa-> intero "FF" 16)

Ma deve essere un modo migliore per farlo.

Risposte:


170

Una funzione può avere più firme se le firme differiscono per arità. Puoi usarlo per fornire valori predefiniti.

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

Si noti che assumendo falsee nilsono entrambi considerati non valori, (if (nil? base) 10 base)potrebbe essere abbreviato (if base base 10)o ulteriormente (or base 10).


3
Penso che sarebbe meglio dire la seconda riga (recur s 10), usando recurinvece di ripetere il nome della funzione string->integer. Ciò renderebbe più semplice rinominare la funzione in futuro. Qualcuno sa qualche motivo per non usare recurin queste situazioni?
Rory O'Kane,

10
Sembra che recurfunzioni solo sulla stessa arità. se hai provato a ripetere in precedenza, ad esempio:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987

Ho affrontato questo stesso problema. Avrebbe senso avere la funzione stessa chiamata (cioè (string->integer s 10))?
Kurt Mueller,

155

Puoi anche distruggere gli restargomenti come una mappa da Clojure 1.2 [ ref ]. Ciò consente di nominare e fornire i valori predefiniti per gli argomenti delle funzioni:

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

Adesso puoi chiamare

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

o

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

Puoi vederlo in azione qui: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (ad esempio)


1
Così facile da capire se proviene da uno sfondo Python :)
Dan

1
Questo è molto più facile da capire rispetto alla risposta accettata ... è questo il modo "clojuriano" accettato ? Si prega di considerare l'aggiunta a questo documento.
Droogans,

1
Ho aggiunto un problema alla guida di stile non ufficiale per aiutare a risolvere questo problema.
Droogans,

1
Questa risposta cattura in modo più efficace il modo "corretto" di farlo rispetto alla risposta accettata, sebbene entrambi funzioneranno bene. (ovviamente, un grande potere delle lingue Lisp è che di solito ci sono molti modi diversi di fare la stessa cosa fondamentale)
johnbakers

2
Mi è sembrato un po 'prolisso e ho avuto difficoltà a ricordarlo per un po', quindi ho creato una macro leggermente meno dettagliata .
Akkiggs

33

Questa soluzione è la più vicina allo spirito della soluzione originale , ma leggermente più pulita

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

Un modello simile che può essere utile utilizza orcombinato conlet

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

Mentre in questo caso è più dettagliato, può essere utile se si desidera che le impostazioni predefinite dipendano da altri valori di input . Ad esempio, considerare la seguente funzione:

(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

Questo approccio può essere facilmente esteso per lavorare con argomenti denominati (come nella soluzione di M. Gilliar):

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

O usando ancora più di una fusione:

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

Se le impostazioni predefinite non sono necessarie in base ad altre impostazioni predefinite (o forse anche in tal caso), la soluzione di Matthew sopra consente anche più valori predefiniti per variabili diverse. È molto più pulito dell'uso di un normaleor
johnbakers

Sono un nocciolo di Clojure quindi forse OpenLearner ha ragione, ma questa è un'alternativa interessante alla soluzione di Matthew sopra. Sono felice di sapere se alla fine decido di usarlo o meno.
GlenPeterson,

orè diverso da :orpoiché ornon conosce la differenza di nile false.
Xiangru Lian,

@XiangruLian Stai dicendo che quando usi: o, se passi false, saprà usare false invece di quello predefinito? Mentre con o userebbe il valore predefinito se passato falso e non falso stesso?
Didier A.

8

C'è un altro approccio che potresti voler prendere in considerazione: funzioni parziali . Questi sono probabilmente un modo più "funzionale" e più flessibile per specificare valori predefiniti per le funzioni.

Inizia creando (se necessario) una funzione che abbia i parametri che desideri fornire come parametri predefiniti come i parametri principali:

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

Questo perché la versione di Clojure di partialconsente di fornire i valori "predefiniti" solo nell'ordine in cui compaiono nella definizione della funzione. Una volta che i parametri sono stati ordinati come desiderato, è quindi possibile creare una versione "predefinita" della funzione utilizzando la partialfunzione:

(partial string->integer 10)

Per rendere questa funzione richiamabile più volte è possibile inserirla in una var usando def:

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

Puoi anche creare un "default locale" usando let:

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

L'approccio della funzione parziale presenta un vantaggio chiave rispetto agli altri: il consumatore della funzione può ancora decidere quale sarà il valore predefinito anziché il produttore della funzione senza dover modificare la definizione della funzione . Questo è illustrato nell'esempio in hexcui ho deciso che la funzione predefinita decimalnon è quella che voglio.

Un altro vantaggio di questo approccio è che è possibile assegnare alla funzione predefinita un nome diverso (decimale, esadecimale, ecc.) Che può essere più descrittivo e / o un ambito diverso (var, locale). Se lo si desidera, la funzione parziale può anche essere miscelata con alcuni degli approcci precedenti:

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

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

(Nota che questo è leggermente diverso dalla risposta di Brian poiché l'ordine dei parametri è stato invertito per i motivi indicati all'inizio di questa risposta)


1
Questo non è ciò che la domanda sta ponendo; è interessante però.
Zaz,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.