Octals ตัวอักษร
มีอยู่ช่วงหนึ่งฉันอ่านในเมทริกซ์ซึ่งใช้เลขศูนย์นำหน้าเพื่อรักษาแถวและคอลัมน์ที่เหมาะสม สิ่งนี้ถูกต้องในทางคณิตศาสตร์เนื่องจากเห็นได้ชัดว่าศูนย์นำหน้าไม่ได้เปลี่ยนค่าพื้นฐาน อย่างไรก็ตามความพยายามที่จะกำหนด var ด้วยเมทริกซ์นี้จะล้มเหลวอย่างลึกลับด้วย:
java.lang.NumberFormatException: Invalid number: 08
ซึ่งทำให้ฉันงงงันโดยสิ้นเชิง เหตุผลก็คือ Clojure ถือว่าค่าจำนวนเต็มตามตัวอักษรโดยมีเลขศูนย์นำหน้าเป็นฐานแปดและไม่มีเลข 08 เป็นฐานแปด
ฉันควรพูดถึงว่า Clojure รองรับค่าฐานสิบหกของ Java แบบดั้งเดิมผ่านคำนำหน้า0x คุณยังสามารถใช้ฐานใดก็ได้ระหว่าง 2 ถึง 36 โดยใช้สัญกรณ์ "base + r + value" เช่น2r101010หรือ36r16ซึ่งเป็น 42 ฐานสิบ
พยายามส่งคืนตัวอักษรในลิเทอรัลของฟังก์ชันที่ไม่ระบุชื่อ
ใช้งานได้:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
ดังนั้นฉันเชื่อว่าสิ่งนี้จะได้ผลเช่นกัน:
(#({%1 %2}) :a 1)
แต่ล้มเหลวด้วย:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
เนื่องจากมาโครตัวอ่าน# ()ถูกขยายเป็น
(fn [%1 %2] ({%1 %2}))
ด้วยตัวอักษรของแผนที่ที่อยู่ในวงเล็บ เนื่องจากเป็นองค์ประกอบแรกจึงถือว่าเป็นฟังก์ชัน (ซึ่งจริงๆแล้วแผนที่ตามตัวอักษรคือ) แต่ไม่มีอาร์กิวเมนต์ที่จำเป็น (เช่นคีย์) ให้ โดยสรุปลิเทอรัลของฟังก์ชันที่ไม่ระบุชื่อจะไม่ขยายเป็น
(fn [%1 %2] {%1 %2})
ดังนั้นคุณจึงไม่สามารถมีค่าตัวอักษร ([],: a, 4,%) เป็นเนื้อความของฟังก์ชันที่ไม่ระบุตัวตนได้
มีคำตอบสองข้อในความคิดเห็น Brian Carperแนะนำให้ใช้ตัวสร้างการใช้งานตามลำดับ (array-map, hash-set, vector) ดังนี้:
(#(array-map %1 %2) :a 1)
ในขณะที่Danแสดงให้เห็นว่าคุณสามารถใช้ฟังก์ชันidentityเพื่อแกะวงเล็บด้านนอก:
(#(identity {%1 %2}) :a 1)
ข้อเสนอแนะของ Brian นำฉันไปสู่ความผิดพลาดครั้งต่อไป ...
การคิดว่าhash-mapหรือarray-map เป็นตัวกำหนดการใช้งานแผนที่คอนกรีตที่ไม่เปลี่ยนแปลง
พิจารณาสิ่งต่อไปนี้:
user> (class (hash-map))
clojure.lang.PersistentArrayMap
user> (class (hash-map :a 1))
clojure.lang.PersistentHashMap
user> (class (assoc (apply array-map (range 2000)) :a :1))
clojure.lang.PersistentHashMap
แม้ว่าโดยทั่วไปคุณจะไม่ต้องกังวลเกี่ยวกับการนำแผนที่ Clojure ไปใช้อย่างเป็นรูปธรรม แต่คุณควรทราบว่าฟังก์ชันที่ขยายแผนที่เช่นAssocหรือconjสามารถใช้PersistentArrayMapและส่งคืนPersistentHashMapซึ่งทำงานได้เร็วกว่าสำหรับแผนที่ขนาดใหญ่
ใช้ฟังก์ชันเป็นจุดเรียกซ้ำแทนการวนซ้ำเพื่อจัดเตรียมการเชื่อมโยงเริ่มต้น
เมื่อฉันเริ่มต้นฉันเขียนฟังก์ชั่นมากมายเช่นนี้:
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
เมื่อในความเป็นจริงการวนซ้ำจะกระชับและเป็นสำนวนสำหรับฟังก์ชันนี้โดยเฉพาะ:
(defn p3 [] {:post [(= % 6857)]}
(loop [i 775147 n 600851475143 times 3]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
สังเกตว่าฉันแทนที่อาร์กิวเมนต์ว่างเนื้อหาฟังก์ชัน "ตัวสร้างเริ่มต้น" (p3 775147 600851475143 3)ด้วยการวนซ้ำ + การโยงเริ่มต้น การเกิดซ้ำในขณะนี้จะย้อนกลับการโยงลูป (แทนที่จะเป็นพารามิเตอร์ fn) และข้ามกลับไปที่จุดเรียกซ้ำ (วนซ้ำแทนที่จะเป็น fn)
อ้างอิง vars "phantom"
ฉันกำลังพูดถึงประเภทของ var ที่คุณอาจกำหนดโดยใช้ REPL - ในระหว่างการเขียนโปรแกรมเชิงสำรวจของคุณ - จากนั้นก็อ้างถึงแหล่งที่มาของคุณโดยไม่รู้ตัว ทุกอย่างทำงานได้ดีจนกว่าคุณจะโหลดเนมสเปซซ้ำ (อาจปิดตัวแก้ไข) และพบสัญลักษณ์ที่ไม่ถูกผูกไว้ที่อ้างอิงตลอดโค้ดของคุณในภายหลัง นอกจากนี้ยังเกิดขึ้นบ่อยครั้งเมื่อคุณปรับโครงสร้างใหม่โดยย้ายตัวแปรจากเนมสเปซหนึ่งไปยังอีกเนมสเปซ
การปฏิบัติต่อรายการเพื่อความเข้าใจเหมือนเป็นสิ่งจำเป็นสำหรับการวนซ้ำ
โดยพื้นฐานแล้วคุณกำลังสร้างรายการขี้เกียจตามรายการที่มีอยู่แทนที่จะดำเนินการวนซ้ำแบบควบคุม Clojure ของdoseqเป็นจริงคล้ายมากขึ้นเพื่อความจำเป็น foreach บ่วงโครงสร้าง
ตัวอย่างหนึ่งของความแตกต่างคือความสามารถในการกรององค์ประกอบที่พวกเขาวนซ้ำโดยใช้เพรดิเคตตามอำเภอใจ:
user> (for [n '(1 2 3 4) :when (even? n)] n)
(2 4)
user> (for [n '(4 3 2 1) :while (even? n)] n)
(4)
อีกวิธีหนึ่งที่แตกต่างออกไปคือสามารถดำเนินการตามลำดับขี้เกียจที่ไม่มีที่สิ้นสุด:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
นอกจากนี้ยังสามารถจัดการกับนิพจน์ที่มีผลผูกพันได้มากกว่าหนึ่งนิพจน์โดยวนซ้ำบนนิพจน์ขวาสุดก่อนและทำงานไปทางซ้าย:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
นอกจากนี้ยังมีไม่ทำลายหรือดำเนินการต่อเพื่อออกก่อนเวลาอันควร
การใช้โครงสร้างมากเกินไป
ฉันมาจากภูมิหลัง OOPish ดังนั้นเมื่อฉันเริ่ม Clojure สมองของฉันยังคงคิดในแง่ของวัตถุ ฉันพบตัวเองการสร้างแบบจำลองทุกอย่างเป็นstructเพราะการจัดกลุ่มของ "สมาชิก" แต่หลวมทำให้ฉันรู้สึกสะดวกสบาย ในความเป็นจริงโครงสร้างส่วนใหญ่ควรได้รับการพิจารณาว่าเป็นการเพิ่มประสิทธิภาพ Clojure จะแบ่งปันคีย์และข้อมูลการค้นหาบางอย่างเพื่อประหยัดหน่วยความจำ นอกจากนี้คุณยังสามารถเพิ่มประสิทธิภาพของพวกเขาด้วยการกำหนดaccessorsเพื่อเพิ่มความเร็วในการค้นหาที่สำคัญ
โดยรวมคุณไม่ได้อะไรจากการใช้structกว่าแผนที่ยกเว้นสำหรับการทำงานเพื่อเพิ่มความซับซ้อนอาจจะไม่คุ้มค่า
ใช้ตัวสร้าง BigDecimal ที่ไม่ได้ใส่น้ำตาล
ฉันต้องการBigDecimalsจำนวนมากและกำลังเขียนโค้ดที่น่าเกลียดเช่นนี้:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
เมื่อในความเป็นจริง Clojure รองรับตัวอักษร BigDecimal โดยต่อท้ายMเข้ากับตัวเลข:
(= (BigDecimal. "42.42") 42.42M)
การใช้เวอร์ชันที่มีน้ำตาลจะช่วยลดการขยายตัวได้มาก ในความคิดเห็นtwilsกล่าวว่าคุณยังสามารถใช้ฟังก์ชัน bigdecและbigintเพื่อให้ชัดเจนยิ่งขึ้น แต่ยังคงกระชับ
การใช้แพ็คเกจ Java ในการตั้งชื่อการแปลงสำหรับเนมสเปซ
นี่ไม่ใช่ความผิดพลาด แต่อย่างใด แต่เป็นสิ่งที่ขัดต่อโครงสร้างสำนวนและการตั้งชื่อของโครงการ Clojure ทั่วไป โครงการ Clojure ที่สำคัญโครงการแรกของฉันมีการประกาศเนมสเปซและโครงสร้างโฟลเดอร์ที่เกี่ยวข้องเช่นนี้:
(ns com.14clouds.myapp.repository)
ซึ่งขยายการอ้างอิงฟังก์ชันที่มีคุณสมบัติครบถ้วนของฉัน:
(com.14clouds.myapp.repository/load-by-name "foo")
เพื่อทำให้สิ่งต่างๆซับซ้อนยิ่งขึ้นฉันใช้โครงสร้างไดเร็กทอรีMavenมาตรฐาน:
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
ซึ่งซับซ้อนกว่าโครงสร้าง Clojure "มาตรฐาน" ของ:
|-- src/
|-- test/
|-- resources/
ซึ่งเป็นค่าเริ่มต้นของโครงการLeiningenและClojureเอง
Maps ใช้ Equals () ของ Java แทน Clojure's = สำหรับการจับคู่คีย์
รายงานเดิมโดยchouserบนIRCการใช้ Java's equals () นี้นำไปสู่ผลลัพธ์ที่ไม่ได้ตั้งใจ:
user> (= (int 1) (long 1))
true
user> ({(int 1) :found} (int 1) :not-found)
:found
user> ({(int 1) :found} (long 1) :not-found)
:not-found
เนื่องจากทั้งจำนวนเต็มและแบบยาวอินสแตนซ์อินสแตนซ์แบบของ 1 ถูกพิมพ์เหมือนกันโดยค่าเริ่มต้นจึงเป็นเรื่องยากที่จะตรวจหาสาเหตุที่แผนที่ของคุณไม่ส่งคืนค่าใด ๆ โดยเฉพาะอย่างยิ่งเมื่อคุณส่งคีย์ของคุณผ่านฟังก์ชั่นซึ่งอาจจะไม่เป็นที่รู้จักสำหรับคุณ แต่จะส่งคืนค่าเป็นเวลานาน
ควรสังเกตว่าการใช้equals ()ของ Java แทน Clojure's =เป็นสิ่งจำเป็นสำหรับแผนที่เพื่อให้สอดคล้องกับอินเทอร์เฟซ java.util.Map
ฉันใช้Programming Clojureโดย Stuart Halloway, Practical Clojureโดย Luke VanderHart และความช่วยเหลือของแฮกเกอร์ Clojure จำนวนนับไม่ถ้วนในIRCและรายชื่อผู้รับจดหมายเพื่อช่วยในการตอบคำถามของฉัน