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.