วิธีสร้างค่าเริ่มต้นสำหรับอาร์กิวเมนต์ของฟังก์ชันใน Clojure


127

ฉันมาพร้อมกับสิ่งนี้:

(defn string-> จำนวนเต็ม [str & [base]]
  (จำนวนเต็ม / parseInt str (ถ้า (ศูนย์? ฐาน) 10 ฐาน)))

(string-> จำนวนเต็ม "10")
(string-> จำนวนเต็ม "FF" 16)

แต่ต้องเป็นวิธีที่ดีกว่านี้

คำตอบ:


170

ฟังก์ชันสามารถมีหลายลายเซ็นได้หากลายเซ็นมีความแตกต่างกัน คุณสามารถใช้เพื่อระบุค่าเริ่มต้น

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

โปรดทราบว่าสมมติfalseและnilมีทั้งที่ถือว่าเป็นค่าที่ไม่(if (nil? base) 10 base)อาจจะลงไปหรือต่อไป(if base base 10)(or base 10)


3
ฉันคิดว่ามันจะดีกว่าสำหรับบรรทัดที่สองที่จะบอกว่า(recur s 10)ใช้แทนการทำซ้ำชื่อฟังก์ชันrecur string->integerซึ่งจะทำให้ง่ายต่อการเปลี่ยนชื่อฟังก์ชันในอนาคต ไม่มีใครรู้เหตุผลที่จะไม่ใช้recurในสถานการณ์เหล่านี้?
Rory O'Kane

10
ดูเหมือนว่าrecurจะใช้งานได้เฉพาะใน arity เดียวกันเท่านั้น หากคุณพยายามเกิดซ้ำข้างต้นเช่นjava.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987

พบปัญหาเดียวกันนี้ มันสมเหตุสมผลหรือไม่ที่จะมีฟังก์ชันเรียกตัวเอง (เช่น(string->integer s 10))?
Kurt Mueller

155

คุณยังสามารถทำลายrestอาร์กิวเมนต์เป็นแผนที่ได้ตั้งแต่ Clojure 1.2 [ ref ] สิ่งนี้ช่วยให้คุณตั้งชื่อและระบุค่าเริ่มต้นสำหรับอาร์กิวเมนต์ของฟังก์ชัน:

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

ตอนนี้คุณสามารถโทร

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

หรือ

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

คุณสามารถดูการดำเนินการได้ที่นี่: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (ตัวอย่าง)


1
เข้าใจง่ายมากถ้ามาจากพื้นหลัง Python :)
Dan

1
นี่เป็นเรื่องง่ายกว่าที่จะเข้าใจคำตอบที่ยอมรับ ... นี่คือวิธี"Clojurian" ที่ยอมรับหรือไม่? โปรดพิจารณาเพิ่มในเอกสารนี้
Droogans

1
ฉันได้เพิ่มปัญหาลงในคู่มือสไตล์ที่ไม่เป็นทางการเพื่อช่วยแก้ไขปัญหานี้
Droogans

1
คำตอบนี้จับวิธีการที่ "ถูกต้อง" ได้อย่างมีประสิทธิภาพมากกว่าคำตอบที่ยอมรับแม้ว่าทั้งสองจะได้ผลดีก็ตาม (แน่นอนพลังที่ยิ่งใหญ่ของภาษาลิสป์คือโดยปกติจะมีหลายวิธีในการทำสิ่งพื้นฐานเดียวกัน)
johnbakers

2
นี้มองพูดมากไปนิดกับผมและผมก็มีปัญหาในการจดจำมันในขณะที่ดังนั้นฉันสร้างขึ้นเล็กน้อย verbose น้อยแมโคร
akbiggs

33

การแก้ปัญหานี้ยิ่งใกล้เคียงกับจิตวิญญาณของโซลูชันดั้งเดิมมากขึ้นแต่ก็สะอาดกว่าเล็กน้อย

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

รูปแบบคล้ายกันซึ่งสามารถใช้ประโยชน์orรวมกับlet

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

ในขณะที่ในกรณีนี้เพิ่มเติมอย่างละเอียดก็จะเป็นประโยชน์ถ้าคุณต้องการที่จะมีค่าเริ่มต้นขึ้นอยู่กับค่าป้อนข้อมูลอื่นตัวอย่างเช่นพิจารณาฟังก์ชันต่อไปนี้:

(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

แนวทางนี้สามารถขยายไปใช้ได้อย่างง่ายดาย อาร์กิวเมนต์ที่ตั้งชื่อ (เช่นเดียวกับในโซลูชันของ M. Gilliar) เช่นกัน:

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

หรือใช้ฟิวชั่นมากยิ่งขึ้น:

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

หากคุณไม่ต้องการให้ค่าเริ่มต้นของคุณขึ้นอยู่กับค่าเริ่มต้นอื่น ๆ (หรือแม้ว่าคุณจะทำก็ตาม) วิธีแก้ปัญหาโดย Matthew ด้านบนยังอนุญาตให้มีค่าเริ่มต้นหลายค่าสำหรับตัวแปรต่างๆ สะอาดกว่าการใช้แบบธรรมดามากor
johnbakers

ฉันเป็น Clojure noob ดังนั้น OpenLearner อาจจะถูกต้อง แต่นี่เป็นทางเลือกที่น่าสนใจสำหรับโซลูชันของ Matthew ด้านบน ฉันดีใจที่ได้ทราบเกี่ยวกับเรื่องนี้ไม่ว่าในท้ายที่สุดฉันจะตัดสินใจใช้หรือไม่ก็ตาม
GlenPeterson

orจะแตกต่างจาก:orตั้งแต่orไม่ทราบความแตกต่างของและnil false
Xiangru Lian

@XiangruLian คุณกำลังบอกว่าเมื่อใช้: หรือถ้าคุณส่งเท็จมันจะรู้ว่าใช้เท็จแทนค่าเริ่มต้น? ในขณะที่มีหรือจะใช้ค่าเริ่มต้นเมื่อส่งผ่านเท็จและไม่เป็นเท็จเอง?
Didier A.

8

มีอีกแนวทางหนึ่งที่คุณอาจต้องการพิจารณา: บางส่วนฟังก์ชันเนื้อหาเหล่านี้เป็นวิธีที่ "ใช้งานได้" และยืดหยุ่นกว่าในการระบุค่าเริ่มต้นสำหรับฟังก์ชัน

เริ่มต้นด้วยการสร้าง (ถ้าจำเป็น) ฟังก์ชันที่มีพารามิเตอร์ที่คุณต้องการให้เป็นค่าเริ่มต้นเป็นพารามิเตอร์นำหน้า:

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

สิ่งนี้ทำได้เนื่องจากเวอร์ชันของ Clojure partialให้คุณระบุค่า "เริ่มต้น" ตามลำดับที่ปรากฏในข้อกำหนดฟังก์ชันเท่านั้น เมื่อเรียงลำดับพารามิเตอร์ตามที่ต้องการแล้วคุณสามารถสร้างเวอร์ชัน "เริ่มต้น" ของฟังก์ชันโดยใช้partialฟังก์ชัน:

(partial string->integer 10)

ในการทำให้ฟังก์ชันนี้สามารถเรียกใช้ได้หลาย ๆ ครั้งคุณสามารถใส่มันลงใน var โดยใช้def:

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

คุณยังสามารถสร้าง "ค่าเริ่มต้นในเครื่อง" โดยใช้let:

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

วิธีการฟังก์ชั่นบางส่วนมีข้อดีอย่างหนึ่งที่สำคัญกว่าคนอื่น ๆ คือผู้บริโภคของฟังก์ชันยังสามารถตัดสินใจเลือกสิ่งที่คุ้มค่าเริ่มต้นจะเป็นมากกว่าผู้ผลิตของฟังก์ชั่นโดยไม่จำเป็นต้องปรับเปลี่ยนการกำหนดฟังก์ชัน นี่คือภาพประกอบในตัวอย่างด้วยhexที่ฉันได้ตัดสินใจว่าฟังก์ชันเริ่มต้นdecimalไม่ใช่สิ่งที่ฉันต้องการ

ข้อดีอีกอย่างของวิธีนี้คือคุณสามารถกำหนดฟังก์ชันเริ่มต้นเป็นชื่ออื่น (ทศนิยมฐานสิบหก ฯลฯ ) ซึ่งอาจสื่อความหมายได้มากกว่าและ / หรือขอบเขตที่แตกต่างกัน (var, local) ฟังก์ชั่นบางส่วนสามารถผสมกับวิธีการบางอย่างข้างต้นได้หากต้องการ:

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

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

(โปรดทราบว่าสิ่งนี้แตกต่างจากคำตอบของ Brian เล็กน้อยเนื่องจากลำดับของพารามิเตอร์ถูกย้อนกลับด้วยเหตุผลที่ระบุไว้ที่ด้านบนของการตอบกลับนี้)


1
นี่ไม่ใช่สิ่งที่คำถามกำลังถามหา เป็นเรื่องที่น่าสนใจแม้ว่า
Zaz

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.