Cara membuat nilai default untuk argumen fungsi di Clojure

127

Saya datang dengan ini:

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

(string-> integer "10")
(string-> integer "FF" 16)

Tetapi ini harus menjadi cara yang lebih baik untuk melakukan ini.

jcubic
sumber

Jawaban:

170

Suatu fungsi dapat memiliki beberapa tanda tangan jika tanda tangan berbeda dalam arity. Anda dapat menggunakannya untuk memberikan nilai default.

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

Perhatikan bahwa asumsi falsedan nilkeduanya dianggap tidak bernilai, (if (nil? base) 10 base)dapat disingkat menjadi (if base base 10), atau lebih jauh (or base 10).

Brian Carper
sumber
3
Saya pikir akan lebih baik untuk baris kedua untuk mengatakan (recur s 10), menggunakan recuralih-alih mengulangi nama fungsi string->integer. Itu akan membuatnya lebih mudah untuk mengubah nama fungsi di masa depan. Adakah yang tahu alasan untuk tidak menggunakan recurdalam situasi ini?
Rory O'Kane
10
Sepertinya recurhanya bekerja pada arity yang sama. jika Anda mencoba berulang di atas, misalnya:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987
Berlari ke masalah yang sama ini. Apakah masuk akal untuk memiliki fungsi memanggil dirinya sendiri (yaitu (string->integer s 10))?
Kurt Mueller
155

Anda juga dapat merusak restargumen sebagai peta sejak Clojure 1.2 [ ref ]. Ini memungkinkan Anda memberi nama dan memberikan default untuk argumen fungsi:

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

Sekarang kamu bisa menelepon

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

atau

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

Anda dapat melihat ini dalam aksi di sini: https://github.com/Raynes/cl[/blob/master/src/cl[/core.clj (misalnya)

Matthew Gilliard
sumber
1
Sangat mudah dimengerti jika berasal dari latar belakang Python :)
Dan
1
Ini jauh lebih mudah dipahami daripada jawaban yang diterima ... apakah ini cara "Clojurian" yang diterima ? Silakan pertimbangkan untuk menambah dokumen ini.
Droogan
1
Saya telah menambahkan masalah ke panduan gaya tidak resmi untuk membantu mengatasi hal ini.
Droogan
1
Jawaban ini lebih efektif menangkap cara "tepat" untuk melakukannya daripada jawaban yang diterima, meskipun keduanya akan bekerja dengan baik. (tentu saja, kekuatan besar bahasa Lisp adalah bahwa biasanya ada banyak cara berbeda untuk melakukan hal mendasar yang sama)
johnbakers
2
Ini tampak agak bertele-tele bagi saya dan saya mengalami kesulitan mengingatnya untuk sementara waktu, jadi saya membuat makro yang sedikit kurang bertele-tele .
akbiggs
33

Solusi ini lebih dekat dengan semangat solusi asli , tetapi sedikit lebih bersih

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

Sebuah pola yang sama yang dapat menggunakan berguna ordikombinasikan denganlet

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

Meskipun dalam hal ini lebih banyak bertele-tele, ini dapat bermanfaat jika Anda ingin memiliki standar bergantung pada nilai input lainnya . Misalnya, pertimbangkan fungsi berikut:

(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

Pendekatan ini dapat dengan mudah diperluas untuk bekerja dengan argumen bernama (seperti dalam solusi M. Gilliar) juga:

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

Atau menggunakan lebih banyak perpaduan:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoarous
sumber
Jika Anda tidak memerlukan standar Anda bergantung pada standar lain (atau mungkin bahkan jika Anda melakukannya), solusi oleh Matthew di atas juga memungkinkan untuk beberapa nilai default untuk variabel yang berbeda. Ini jauh lebih bersih daripada menggunakan yang biasaor
johnbakers
Saya seorang Clojure noob, jadi mungkin OpenLearner benar, tetapi ini merupakan alternatif yang menarik untuk solusi Matthew di atas. Saya senang mengetahui tentang ini apakah saya akhirnya memutuskan untuk menggunakannya atau tidak.
GlenPeterson
orberbeda dari :orkarena ortidak tahu perbedaan nildan false.
Xiangru Lian
@XiangruLian Apakah Anda mengatakan bahwa ketika menggunakan: atau, jika Anda memberikan false, ia akan tahu menggunakan false sebagai ganti default? Sementara dengan atau itu akan menggunakan default ketika melewati false dan bukan false itu sendiri?
Didier A.
8

Ada pendekatan lain yang mungkin ingin Anda pertimbangkan: fungsi parsial . Ini bisa dibilang cara yang lebih "fungsional" dan lebih fleksibel untuk menentukan nilai default untuk fungsi.

Mulailah dengan membuat (jika perlu) fungsi yang memiliki parameter yang ingin Anda berikan sebagai default sebagai parameter utama:

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

Ini dilakukan karena versi Clojure partialmemungkinkan Anda memberikan nilai "default" hanya dalam urutan yang muncul dalam definisi fungsi. Setelah parameter telah dipesan sesuai keinginan, Anda kemudian dapat membuat versi "default" dari fungsi menggunakan partialfungsi:

(partial string->integer 10)

Untuk membuat fungsi ini bisa dipanggil beberapa kali, Anda bisa meletakkannya di var menggunakan def:

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

Anda juga dapat membuat "default lokal" menggunakan let:

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

Pendekatan fungsi parsial memiliki satu keunggulan utama dibandingkan yang lain: konsumen fungsi masih dapat memutuskan apa nilai default akan menjadi daripada produsen fungsi tanpa perlu memodifikasi definisi fungsi . Ini diilustrasikan dalam contoh di hexmana saya telah memutuskan bahwa fungsi default decimalbukan yang saya inginkan.

Keuntungan lain dari pendekatan ini adalah Anda dapat menetapkan fungsi default nama yang berbeda (desimal, hex, dll) yang mungkin lebih deskriptif dan / atau cakupan yang berbeda (var, lokal). Fungsi parsial juga dapat dicampur dengan beberapa pendekatan di atas jika diinginkan:

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

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

(Perhatikan ini sedikit berbeda dari jawaban Brian karena urutan parameter telah dibalik karena alasan yang diberikan di bagian atas respons ini)

optevo
sumber
1
Ini bukan pertanyaan yang ditanyakan; itu menarik.
Zaz