ข้อผิดพลาดทั่วไปในการเขียนโปรแกรมสำหรับนักพัฒนา Clojure เพื่อหลีกเลี่ยง [ปิด]


92

นักพัฒนา Clojure เกิดข้อผิดพลาดทั่วไปอะไรบ้างและเราจะหลีกเลี่ยงได้อย่างไร

ตัวอย่างเช่น; มาใหม่เพื่อ Clojure คิดว่าฟังก์ชั่นการทำงานเช่นเดียวกับcontains? java.util.Collection#containsอย่างไรก็ตามcontains?จะทำงานในลักษณะเดียวกันเมื่อใช้กับคอลเล็กชันที่จัดทำดัชนีเช่นแผนที่และชุดและคุณกำลังมองหาคีย์ที่ระบุ:

(contains? {:a 1 :b 2} :b)
;=> true
(contains? {:a 1 :b 2} 2)
;=> false
(contains? #{:a 1 :b 2} :b)
;=> true

เมื่อใช้กับคอลเลกชันที่จัดทำดัชนีเป็นตัวเลข (เวกเตอร์อาร์เรย์) contains? จะตรวจสอบเฉพาะว่าองค์ประกอบที่ระบุอยู่ในช่วงดัชนีที่ถูกต้อง (อิงศูนย์):

(contains? [1 2 3 4] 4)
;=> false
(contains? [1 2 3 4] 0)
;=> true

หากได้รับรายชื่อcontains?จะไม่ส่งคืนจริง


4
เพียงแค่ FYI สำหรับนักพัฒนา Clojure ที่กำลังมองหาjava.util.Collection # มีฟังก์ชันประเภทตรวจสอบclojure.contrib.seq-utils / includes? จากเอกสาร: การใช้งาน: (รวมถึง? coll x) ส่งคืนค่าจริงหาก coll มีค่าเท่ากับ (ด้วย =) ถึง x ในเวลาเชิงเส้น
Robert Campbell

11
ดูเหมือนคุณจะพลาดความจริงที่ว่าคำถามเหล่านั้นคือ Community Wiki

3
ฉันชอบที่คำถาม Perl ต้องก้าวข้ามกับคนอื่น ๆ ทั้งหมด :)
Ether

8
สำหรับนักพัฒนา Clojure ที่กำลังมองหามีฉันไม่แนะนำให้ทำตามคำแนะนำของ rcampbell seq-utils ได้เลิกใช้งานไปนานแล้วและฟังก์ชันนั้นไม่เคยมีประโยชน์ในการเริ่มต้น คุณสามารถใช้someฟังก์ชันของ Clojure หรือใช้containsตัวมันเอง คอลเลกชัน Clojure ใช้งานjava.util.Collectionได้ (.contains [1 2 3] 2) => true
Rayne

คำตอบ:


70

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})  ; notice the lack of parenthesis

ดังนั้นคุณจึงไม่สามารถมีค่าตัวอักษร ([],: 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ซึ่งทำงานได้เร็วกว่าสำหรับแผนที่ขนาดใหญ่


ใช้ฟังก์ชันเป็นจุดเรียกซ้ำแทนการวนซ้ำเพื่อจัดเตรียมการเชื่อมโยงเริ่มต้น

เมื่อฉันเริ่มต้นฉันเขียนฟังก์ชั่นมากมายเช่นนี้:

; Project Euler #3
(defn p3 
  ([] (p3 775147 600851475143 3))
  ([i n times]
    (if (and (divides? i n) (fast-prime? i times)) i
      (recur (dec i) n times))))

เมื่อในความเป็นจริงการวนซ้ำจะกระชับและเป็นสำนวนสำหรับฟังก์ชันนี้โดยเฉพาะ:

; Elapsed time: 387 msecs
(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) ; true

การใช้เวอร์ชันที่มีน้ำตาลจะช่วยลดการขยายตัวได้มาก ในความคิดเห็น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และรายชื่อผู้รับจดหมายเพื่อช่วยในการตอบคำถามของฉัน


1
มาโครตัวอ่านทั้งหมดมีเวอร์ชันฟังก์ชันปกติ คุณสามารถทำได้(#(hash-set %1 %2) :a 1)หรือในกรณี(hash-set :a 1)นี้
Brian Carper

2
คุณยังสามารถ "ลบ" วงเล็บเพิ่มเติมที่มีข้อมูลประจำตัว: (# (identity {% 1% 2}): a 1)

1
คุณยังสามารถใช้do: (#(do {%1 %2}) :a 1).
Michał Marczyk

@ Michał - ฉันไม่ชอบการแก้ปัญหานี้มากที่สุดเท่าที่คนก่อนหน้านี้เพราะทำหมายความว่าผลข้างเคียงที่เกิดขึ้นในเมื่อความจริงนี้ไม่ได้เป็นกรณีที่นี่
Robert Campbell

@ rrc7cz: ในความเป็นจริงไม่จำเป็นต้องใช้ฟังก์ชันนิรนามที่นี่เลยเนื่องจากการใช้hash-mapโดยตรง (เช่นเดียวกับ(hash-map :a 1)หรือ(map hash-map keys vals)) สามารถอ่านได้มากกว่าและไม่ได้หมายความว่ามีบางสิ่งที่พิเศษและยังไม่ได้นำไปใช้ในฟังก์ชันที่มีชื่อ กำลังเกิดขึ้น (ซึ่งการใช้#(...)หมายความว่าฉันพบ) ในความเป็นจริงการใช้ fns ที่ไม่ระบุตัวตนมากเกินไปเป็นเรื่องที่น่าสนใจในตัวเอง :-) OTOH บางครั้งฉันใช้doในฟังก์ชั่นที่ไม่ระบุตัวตนที่รัดกุมเป็นพิเศษซึ่งไม่มีผลข้างเคียง ... มีแนวโน้มที่จะเห็นได้ชัดว่าพวกเขามองแวบเดียว เรื่องของรสชาติฉันเดา
Michał Marczyk

42

ลืมที่จะบังคับให้ประเมิน seqs ขี้เกียจ

ระบบจะไม่ประเมิน Lazy seqs เว้นแต่คุณจะขอให้ประเมิน คุณอาจคาดหวังว่าสิ่งนี้จะพิมพ์บางสิ่งบางอย่าง แต่ไม่ได้

user=> (defn foo [] (map println [:foo :bar]) nil)
#'user/foo
user=> (foo)
nil

mapไม่เคยได้รับการประเมินก็ทิ้งอย่างเงียบ ๆ เพราะมันขี้เกียจ คุณต้องใช้อย่างใดอย่างหนึ่งdoseq, dorun, doallฯลฯ ที่จะบังคับให้การประเมินผลของลำดับขี้เกียจสำหรับผลข้างเคียง

user=> (defn foo [] (doseq [x [:foo :bar]] (println x)) nil)
#'user/foo
user=> (foo)
:foo
:bar
nil
user=> (defn foo [] (dorun (map println [:foo :bar])) nil)
#'user/foo
user=> (foo)
:foo
:bar
nil

การใช้แบบ bare mapที่ REPL ดูเหมือนจะใช้ได้ แต่ใช้ได้ผลเพราะ REPL บังคับให้ประเมินค่า lazy seq เองเท่านั้น สิ่งนี้สามารถทำให้จุดบกพร่องสังเกตได้ยากขึ้นเนื่องจากโค้ดของคุณทำงานที่ REPL และไม่ทำงานจากไฟล์ต้นฉบับหรือภายในฟังก์ชัน

user=> (map println [:foo :bar])
(:foo
:bar
nil nil)

1
+1. สิ่งนี้ทำให้ฉันรู้สึกแย่ แต่ในลักษณะที่ร้ายกาจมากขึ้น: ฉันกำลังประเมิน(map ...)จากภายใน(binding ...)และสงสัยว่าเหตุใดจึงใช้ค่าการผูกใหม่ไม่ได้
Alex B

20

ฉันเป็น Clojure noob ผู้ใช้ขั้นสูงอาจมีปัญหาที่น่าสนใจมากขึ้น

พยายามพิมพ์ลำดับขี้เกียจที่ไม่มีที่สิ้นสุด

ฉันรู้ว่าฉันกำลังทำอะไรกับลำดับที่ขี้เกียจของฉัน แต่เพื่อจุดประสงค์ในการดีบักฉันได้แทรกการโทร print / prn / pr บางส่วนโดยลืมไปชั่วคราวว่ากำลังพิมพ์อะไรอยู่ ตลกดีทำไมพีซีของฉันถึงหยุดทำงานทั้งหมด

พยายามตั้งโปรแกรม Clojure อย่างจำเป็น

มีการล่อลวงให้สร้างrefs หรือatoms จำนวนมากและเขียนโค้ดที่ทำให้รัฐของพวกเขายุ่งเหยิงอยู่ตลอดเวลา สิ่งนี้สามารถทำได้ แต่ก็ไม่เหมาะ นอกจากนี้ยังอาจมีประสิทธิภาพต่ำและไม่ค่อยได้รับประโยชน์จากหลายคอร์

พยายามตั้งโปรแกรม Clojure 100% ตามหน้าที่

พลิกกลับด้านนี้: อัลกอริทึมบางอย่างต้องการสถานะที่ไม่แน่นอนเล็กน้อย การหลีกเลี่ยงสถานะที่ไม่แน่นอนในทางศาสนาอาจส่งผลให้อัลกอริทึมช้าหรืออึดอัด ต้องใช้วิจารณญาณและประสบการณ์เล็กน้อยในการตัดสินใจ

พยายามทำมากเกินไปใน Java

เนื่องจากการเข้าถึง Java เป็นเรื่องง่ายมากบางครั้งจึงน่าสนใจที่จะใช้ Clojure เป็นเครื่องห่อภาษาสคริปต์รอบ ๆ Java แน่นอนว่าคุณจะต้องทำสิ่งนี้อย่างแน่นอนเมื่อใช้การทำงานของไลบรารี Java แต่มีความรู้สึกเพียงเล็กน้อยใน (เช่น) การรักษาโครงสร้างข้อมูลใน Java หรือใช้ชนิดข้อมูล Java เช่นคอลเล็กชันที่มีค่าเทียบเท่าที่ดีใน Clojure


13

มากมายที่กล่าวมาแล้ว ฉันจะเพิ่มอีกหนึ่ง

Clojure ถ้าถือว่า Java Boolean object เป็นจริงเสมอแม้ว่าค่าจะเป็นเท็จก็ตาม ดังนั้นหากคุณมีฟังก์ชัน java land ที่ส่งคืนค่า java Boolean ตรวจสอบให้แน่ใจว่าคุณไม่ได้ตรวจสอบโดยตรง (if java-bool "Yes" "No") แต่ (if (boolean java-bool) "Yes" "No")ต้องการ

ฉันถูกเผาโดยสิ่งนี้ด้วยไลบรารี clojure.contrib.sql ที่ส่งคืนฟิลด์บูลีนฐานข้อมูลเป็นวัตถุบูลีน java


8
โปรดทราบ(if java.lang.Boolean/FALSE (println "foo"))ว่าไม่ได้พิมพ์ foo (if (java.lang.Boolean. "false") (println "foo"))ไม่แม้ว่า(if (boolean (java.lang.Boolean "false")) (println "foo"))จะไม่ ... ค่อนข้างสับสนแน่นอน!
Michał Marczyk

ดูเหมือนว่าจะทำงานตามที่คาดไว้ใน Clojure 1.4.0: (ยืนยัน (=: เท็จ (ถ้าบูลีน / FALSE: จริง: เท็จ)))
Jakub Holý

เมื่อเร็ว ๆ นี้ฉันยังถูกไฟไหม้เมื่อทำ (ตัวกรอง: mykey coll) โดยที่: ค่าของ mykey ที่บูลีน - ทำงานตามที่คาดไว้กับคอลเลกชันที่สร้างขึ้นโดย Clojure แต่ไม่ได้อยู่ในคอลเล็กชันที่ไม่ได้ซีเรียลไลซ์เมื่อทำให้เป็นอนุกรมโดยใช้การทำให้เป็นอนุกรมของ Java เริ่มต้น - เนื่องจากบูลีนเหล่านั้นถูก deserialized เป็นบูลีนใหม่ () และน่าเศร้า (บูลีนใหม่ (จริง)! = java.lang.Boolean / TRUE)
Hendekagon

1
เพียงจำกฎพื้นฐานของค่าบูลีนใน Clojure - nilและfalseเป็นเท็จและทุกอย่างเป็นจริง Java Booleanไม่ใช่nilและไม่ใช่false(เพราะเป็นวัตถุ) ดังนั้นพฤติกรรมจึงสอดคล้องกัน
erikprice

13

รักษาศีรษะของคุณในลูป
คุณมีความเสี่ยงที่จะหมดหน่วยความจำหากคุณวนซ้ำองค์ประกอบของลำดับที่อาจมีขนาดใหญ่มากหรือไม่สิ้นสุดและขี้เกียจในขณะที่ยังคงอ้างอิงถึงองค์ประกอบแรก

การลืมว่าไม่มี TCO
การโทรหางแบบปกติใช้พื้นที่สแต็กและจะล้นหากคุณไม่ระวัง Clojure มี'recurและ'trampolineเพื่อจัดการกับหลาย ๆ กรณีที่จะใช้การเรียกหางที่เหมาะสมที่สุดในภาษาอื่น ๆ แต่ต้องใช้เทคนิคเหล่านี้โดยเจตนา

ลำดับที่ไม่ค่อนข้างขี้เกียจ
คุณอาจสร้างลำดับขี้เกียจด้วย'lazy-seqหรือ'lazy-cons(หรือสร้างตาม lazy API ระดับที่สูงขึ้น) แต่ถ้าคุณรวม'vecหรือส่งผ่านฟังก์ชั่นอื่น ๆ ที่ตระหนักถึงลำดับก็จะไม่ขี้เกียจอีกต่อไป ทั้งสแตกและฮีปสามารถล้นได้ด้วยสิ่งนี้

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


4
สาขาการพัฒนาที่กำลังจะมาถึงจะช่วยลดรายการแรกโดยการลบการอ้างอิงถึงอ็อบเจ็กต์ในฟังก์ชันเมื่อไม่สามารถเข้าถึงได้ภายในเครื่อง
Arthur Ulfeldt

9

ใช้loop ... recurเพื่อประมวลผลลำดับเมื่อแผนที่จะทำ

(defn work [data]
    (do-stuff (first data))
    (recur (rest data)))

เทียบกับ

(map do-stuff data)

ฟังก์ชันแผนที่ (ในสาขาล่าสุด) ใช้ลำดับแบบเป็นกลุ่มและการปรับให้เหมาะสมอื่น ๆ อีกมากมาย นอกจากนี้เนื่องจากมีการเรียกใช้ฟังก์ชันนี้บ่อย Hotspot JIT จึงได้รับการปรับแต่งให้เหมาะสมและพร้อมที่จะใช้งานโดยไม่มี "เวลาอุ่นเครื่อง" ใด ๆ


1
สองเวอร์ชันนี้ไม่เทียบเท่ากัน workฟังก์ชันของคุณเทียบเท่ากับ(doseq [item data] (do-stuff item)). (นอกจากความจริงแล้วการทำงานวนซ้ำไปมาไม่สิ้นสุด)
kotarak

ใช่คนแรกทำลายความเกียจคร้านในการโต้แย้ง seq ที่ได้จะมีค่าเท่ากันแม้ว่าจะไม่ใช่ seq ที่ขี้เกียจอีกต่อไป
Arthur Ulfeldt

+1! ผมเขียน recursive ฟังก์ชันขนาดเล็กจำนวนมากเพียงเพื่อจะพบว่าวันอื่นทั้งหมดเหล่านี้จะได้รับการทั่วไปโดยใช้และmap / หรือ reduce
mike3996

5

ประเภทการรวบรวมมีพฤติกรรมที่แตกต่างกันสำหรับการดำเนินการบางอย่าง:

user=> (conj '(1 2 3) 4)    
(4 1 2 3)                 ;; new element at the front
user=> (conj [1 2 3] 4) 
[1 2 3 4]                 ;; new element at the back

user=> (into '(3 4) (list 5 6 7))
(7 6 5 3 4)
user=> (into [3 4] (list 5 6 7)) 
[3 4 5 6 7]

การทำงานกับสตริงอาจทำให้สับสนได้ (ฉันยังไม่ค่อยเข้าใจ) โดยเฉพาะสตริงไม่เหมือนกับลำดับของอักขระแม้ว่าฟังก์ชันลำดับจะทำงานกับสตริงเหล่านี้:

user=> (filter #(> (int %) 96) "abcdABCDefghEFGH")
(\a \b \c \d \e \f \g \h)

ในการดึงสตริงออกมาคุณต้องทำ:

user=> (apply str (filter #(> (int %) 96) "abcdABCDefghEFGH"))
"abcdefgh"

3

parantheses มากเกินไปโดยเฉพาะอย่างยิ่งกับ void java method call ภายในซึ่งส่งผลให้ NPE:

public void foo() {}

((.foo))

ผลลัพธ์ใน NPE จาก paranthes ภายนอกเนื่องจาก parantheses ภายในประเมินเป็นศูนย์

public int bar() { return 5; }

((.bar)) 

ส่งผลให้แก้ไขข้อบกพร่องได้ง่ายขึ้น:

java.lang.Integer cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.