Câu trả lời:
Một chức năng có thể có nhiều chữ ký nếu chữ ký khác nhau về arity. Bạn có thể sử dụng điều đó để cung cấp các giá trị mặc định.
(defn string->integer
([s] (string->integer s 10))
([s base] (Integer/parseInt s base)))
Lưu ý rằng giả định false
và nil
cả hai đều được coi là không có giá trị, (if (nil? base) 10 base)
có thể được rút ngắn (if base base 10)
hoặc xa hơn (or base 10)
.
recur
chỉ hoạt động trên cùng một arity. nếu bạn đã thử tái diễn ở trên, ví dụ:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
(string->integer s 10)
)?
Bạn cũng có thể hủy cấu trúc các rest
đối số dưới dạng bản đồ kể từ Clojure 1.2 [ ref ]. Điều này cho phép bạn đặt tên và cung cấp mặc định cho các đối số chức năng:
(defn string->integer [s & {:keys [base] :or {base 10}}]
(Integer/parseInt s base))
Bây giờ bạn có thể gọi
(string->integer "11")
=> 11
hoặc là
(string->integer "11" :base 8)
=> 9
Bạn có thể thấy điều này trong hành động ở đây: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (ví dụ)
Giải pháp này gần với tinh thần của giải pháp ban đầu , nhưng sạch hơn một chút
(defn string->integer [str & [base]]
(Integer/parseInt str (or base 10)))
Một mô hình tương tự có thể được sử dụng tiện dụng or
kết hợp vớilet
(defn string->integer [str & [base]]
(let [base (or base 10)]
(Integer/parseInt str base)))
Mặc dù trong trường hợp này dài dòng hơn, nó có thể hữu ích nếu bạn muốn mặc định phụ thuộc vào các giá trị đầu vào khác . Ví dụ, hãy xem xét chức năng sau:
(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
Cách tiếp cận này có thể dễ dàng được mở rộng để làm việc với các đối số được đặt tên (như trong giải pháp của M. Gilliar):
(defn exemplar [a & {:keys [b c]}]
(let [b (or b 5)
c (or c (* 7 b))]
(println a b c)))
Hoặc sử dụng nhiều hơn một phản ứng tổng hợp:
(defn exemplar [a & {:keys [b c] :or {b 5}}]
(let [c (or c (* 7 b))]
(println a b c)))
or
or
khác với :or
vì or
không biết sự khác biệt của nil
và false
.
Có một cách tiếp cận khác mà bạn có thể muốn xem xét: các hàm một phần . Đây được cho là một cách "chức năng" hơn và linh hoạt hơn để chỉ định các giá trị mặc định cho các chức năng.
Bắt đầu bằng cách tạo (nếu cần) một hàm có (các) tham số mà bạn muốn cung cấp làm (các) tham số mặc định làm (các) tham số hàng đầu:
(defn string->integer [base str]
(Integer/parseInt str base))
Điều này được thực hiện bởi vì phiên bản của Clojure partial
cho phép bạn cung cấp các giá trị "mặc định" chỉ theo thứ tự chúng xuất hiện trong định nghĩa hàm. Khi các tham số đã được sắp xếp theo mong muốn, bạn có thể tạo phiên bản "mặc định" của hàm bằng partial
hàm:
(partial string->integer 10)
Để làm cho chức năng này có thể gọi được nhiều lần, bạn có thể đặt nó vào một var bằng cách sử dụng def
:
(def decimal (partial string->integer 10))
(decimal "10")
;10
Bạn cũng có thể tạo "mặc định cục bộ" bằng cách sử dụng let
:
(let [hex (partial string->integer 16)]
(* (hex "FF") (hex "AA")))
;43350
Cách tiếp cận hàm một phần có một lợi thế chính so với các cách khác: người tiêu dùng hàm vẫn có thể quyết định giá trị mặc định sẽ là gì hơn là nhà sản xuất hàm mà không cần sửa đổi định nghĩa hàm . Điều này được minh họa trong ví dụ với hex
nơi tôi đã quyết định rằng hàm mặc định decimal
không phải là thứ tôi muốn.
Một ưu điểm khác của phương pháp này là bạn có thể gán cho hàm mặc định một tên khác (thập phân, hex, v.v.) có thể mô tả nhiều hơn và / hoặc một phạm vi khác (var, local). Hàm một phần cũng có thể được trộn lẫn với một số cách tiếp cận ở trên nếu muốn:
(defn string->integer
([s] (string->integer s 10))
([base s] (Integer/parseInt s base)))
(def hex (partial string->integer 16))
(Lưu ý điều này hơi khác so với câu trả lời của Brian vì thứ tự của các tham số đã bị đảo ngược vì các lý do được đưa ra ở đầu phản hồi này)
Bạn cũng có thể xem (fnil)
https://clojuredocs.org/clojure.core/fnil
(recur s 10)
, sử dụngrecur
thay vì lặp lại tên hàmstring->integer
. Điều đó sẽ làm cho việc đổi tên chức năng trong tương lai dễ dàng hơn. Có ai biết lý do không sử dụngrecur
trong những tình huống này?