Cách tạo giá trị mặc định cho đối số hàm trong Clojure


127

Tôi đi kèm với điều này:

(defn chuỗi-> số nguyên [str & [cơ sở]]
  (Số nguyên / parseInt str (if (nil? Base) 10 cơ sở)))

(chuỗi-> số nguyên "10")
(chuỗi-> số nguyên "FF" 16)

Nhưng nó phải là một cách tốt hơn để làm điều này.

Câu trả lời:


170

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 falsenilcả 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).


3
Tôi nghĩ rằng sẽ tốt hơn cho dòng thứ hai để nói (recur s 10), sử dụng recurthay vì lặp lại tên hàm string->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ụng recurtrong những tình huống này?
Rory O'Kane

10
Có vẻ như recurchỉ 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:
djhaskin987

Ran vào vấn đề tương tự. Nó sẽ chỉ có ý nghĩa để có chức năng gọi chính nó (tức là (string->integer s 10))?
Kurt Mueller

155

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ụ)


1
Rất dễ hiểu nếu đến từ nền Python :)
Dan

1
Điều này dễ hiểu hơn nhiều so với câu trả lời được chấp nhận ... đây có phải là cách "Clojurian" được chấp nhận ? Vui lòng xem xét thêm vào tài liệu này.
Dropogans

1
Tôi đã thêm một vấn đề vào hướng dẫn phong cách không chính thức để giúp giải quyết vấn đề này.
Dropogans

1
Câu trả lời này nắm bắt hiệu quả hơn cách thức "phù hợp" để làm điều đó hơn câu trả lời được chấp nhận, mặc dù cả hai sẽ hoạt động tốt. (tất nhiên, một sức mạnh lớn của các ngôn ngữ Lisp là thường có nhiều cách khác nhau để làm cùng một việc cơ bản)
johnbakers

2
Điều này có vẻ hơi dài dòng đối với tôi và tôi đã gặp khó khăn khi nhớ nó trong một thời gian, vì vậy tôi đã tạo ra một macro ít dài dòng hơn một chút .
akbiggs

33

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 orkế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)))

Nếu bạn không cần mặc định của mình phụ thuộc vào các mặc định khác (hoặc có thể ngay cả khi bạn làm), giải pháp của Matthew ở trên cũng cho phép nhiều giá trị mặc định cho các biến khác nhau. Nó sạch sẽ hơn nhiều so với việc sử dụng thường xuyênor
johnbakers

Tôi là một Clojure noob nên có lẽ OpenLearner đúng, nhưng đây là một giải pháp thay thế thú vị cho giải pháp của Matthew ở trên. Tôi rất vui khi biết về điều này cho dù cuối cùng tôi quyết định sử dụng nó hay không.
GlenPeterson

orkhác với :ororkhông biết sự khác biệt của nilfalse.
Xiangru Lian

@XiangruLian Bạn có nói rằng khi sử dụng: hoặc, nếu bạn chuyển sai, nó sẽ biết sử dụng false thay vì mặc định? Trong khi với hoặc nó sẽ sử dụng mặc định khi thông qua false và không sai chính nó?
Didier A.

8

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 partialcho 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 partialhà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 hexnơi tôi đã quyết định rằng hàm mặc định decimalkhô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)


1
Đây không phải là những gì câu hỏi đang yêu cầu; nó là thú vị mặc dù
Zaz

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.