Clojure: ลดเทียบกับใช้


126

ฉันเข้าใจความแตกต่างของแนวคิดระหว่างreduceและapply:

(reduce + (list 1 2 3 4 5))
; translates to: (+ (+ (+ (+ 1 2) 3) 4) 5)

(apply + (list 1 2 3 4 5))
; translates to: (+ 1 2 3 4 5)

อย่างไรก็ตาม clojure สำนวนใดมากกว่ากัน? มันสร้างความแตกต่างไม่ทางใดก็ทางหนึ่ง? จากการทดสอบประสิทธิภาพ (แบบ จำกัด ) ของฉันดูเหมือนว่าreduceจะเร็วขึ้นเล็กน้อย

คำตอบ:


125

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

+ถูกนำไปใช้ในรูปแบบของreduceตัวแปร arity case (มากกว่า 2 อาร์กิวเมนต์) อันที่จริงสิ่งนี้ดูเหมือนจะเป็นวิธี "เริ่มต้น" ที่สมเหตุสมผลอย่างยิ่งในการใช้ฟังก์ชันการเชื่อมโยงตัวแปรใด ๆ : reduceมีศักยภาพในการเพิ่มประสิทธิภาพบางอย่างเพื่อเร่งความเร็ว - อาจจะผ่านบางสิ่งเช่นinternal-reduceแปลกใหม่ 1.2 ที่เพิ่งปิดใช้งานในต้นแบบ แต่ หวังว่าจะได้รับการแนะนำใหม่ในอนาคตซึ่งจะเป็นเรื่องโง่ที่จะทำซ้ำในทุกฟังก์ชันซึ่งอาจได้รับประโยชน์จากพวกเขาในกรณี vararg ในกรณีทั่วไปapplyจะเพิ่มค่าโสหุ้ยเล็กน้อย (โปรดทราบว่าไม่มีอะไรน่าเป็นห่วงจริงๆ)

บนมืออื่น ๆ , ฟังก์ชั่นที่ซับซ้อนอาจใช้ประโยชน์จากโอกาสการเพิ่มประสิทธิภาพบางอย่างที่ไม่ได้ทั่วไปพอที่จะสร้างขึ้นในreduce; จากนั้นapplyจะช่วยให้คุณใช้ประโยชน์จากสิ่งเหล่านั้นในขณะที่reduceอาจทำให้คุณช้าลง เป็นตัวอย่างที่ดีของการเกิดขึ้นหลังสถานการณ์ในทางปฏิบัติที่ให้บริการโดยstr: จะใช้StringBuilderภายในและจะได้รับประโยชน์อย่างมีนัยสำคัญจากการใช้มากกว่าapplyreduce

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


คำตอบที่ดี หมายเหตุด้านข้างทำไมไม่รวมsumฟังก์ชันในตัวเช่นใน haskell? ดูเหมือนจะเป็นการดำเนินการทั่วไป
dbyrne

17
ขอบคุณยินดีที่ได้ทราบ! Re: sumฉันจะบอกว่า Clojure มีฟังก์ชั่นนี้มันถูกเรียก+และคุณสามารถใช้กับapply. :-) พูดอย่างจริงจังฉันคิดว่าใน Lisp โดยทั่วไปถ้ามีฟังก์ชั่นแบบแปรผันมักจะไม่มาพร้อมกับกระดาษห่อหุ้มที่ทำงานบนคอลเลกชันนั่นคือสิ่งที่คุณใช้applyสำหรับ (หรือreduceถ้าคุณรู้ว่าเหมาะสมกว่า)
Michał Marczyk

6
ตลกดีคำแนะนำของฉันตรงกันข้าม: reduceเมื่อมีข้อสงสัยapplyเมื่อคุณรู้แน่นอนว่ามีการเพิ่มประสิทธิภาพ reduceสัญญามีความแม่นยำมากขึ้นและมีแนวโน้มที่จะเพิ่มประสิทธิภาพทั่วไป applyมีความคลุมเครือมากกว่าดังนั้นจึงสามารถปรับให้เหมาะสมเป็นกรณี ๆ ไปเท่านั้น strและconcatเป็นสองข้อยกเว้นที่แพร่หลาย
cgrand

1
@cgrand การจัดเรียงใหม่ของเหตุผลของฉันอาจจะประมาณว่าสำหรับฟังก์ชันที่reduceและapplyเทียบเท่าในแง่ของผลลัพธ์ฉันคาดหวังว่าผู้เขียนฟังก์ชันที่เป็นปัญหาจะทราบวิธีที่ดีที่สุดในการเพิ่มประสิทธิภาพการโอเวอร์โหลดตัวแปรของพวกเขาและเพียงแค่นำไปใช้ในแง่ของreduceif นั่นคือสิ่งที่เหมาะสมที่สุด (ตัวเลือกในการทำเช่นนั้นมีให้บริการเสมอและทำให้เป็นค่าเริ่มต้นที่เหมาะสมอย่างเด่นชัด) ฉันเห็นว่าคุณมาจากreduceไหนเป็นศูนย์กลางของเรื่องราวการแสดงของ Clojure (และเพิ่มมากขึ้นเรื่อย ๆ ) ได้รับการปรับให้เหมาะสมอย่างมากและระบุไว้อย่างชัดเจน
Michał Marczyk

51

สำหรับมือใหม่ที่กำลังมองหาคำตอบนี้โปรด
ระวังคำตอบที่ไม่เหมือนกัน:

(apply hash-map [:a 5 :b 6])
;= {:a 5, :b 6}
(reduce hash-map [:a 5 :b 6])
;= {{{:a 5} :b} 6}

21

ความคิดเห็นแตกต่างกันไป - ในโลก Lisp ที่ยิ่งใหญ่กว่าreduceนั้นถือว่าเป็นสำนวนมากกว่า ประการแรกมีปัญหาเกี่ยวกับตัวแปรที่กล่าวถึงแล้ว นอกจากนี้คอมไพเลอร์ Common Lisp บางตัวจะล้มเหลวเมื่อapplyถูกนำไปใช้กับรายการที่ยาวมากเนื่องจากวิธีจัดการกับรายการอาร์กิวเมนต์

อย่างไรก็ตามในบรรดา Clojurists ในแวดวงของฉันการใช้applyในกรณีนี้ดูเหมือนจะเป็นเรื่องธรรมดา ฉันพบว่ามันง่ายกว่าที่จะครางและชอบมันด้วย


19

มันไม่ได้สร้างความแตกต่างในกรณีนี้เนื่องจาก + เป็นกรณีพิเศษที่สามารถใช้กับอาร์กิวเมนต์จำนวนเท่าใดก็ได้ ลดเป็นวิธีการใช้ฟังก์ชันที่คาดว่าจะมีอาร์กิวเมนต์จำนวนคงที่ (2) กับรายการอาร์กิวเมนต์แบบยาวโดยพลการ


9

โดยปกติแล้วฉันมักจะพบว่าตัวเองชอบลดเวลาที่จะแสดงคอลเลคชันประเภทใดก็ตาม - มันทำงานได้ดีและเป็นฟังก์ชันที่มีประโยชน์โดยทั่วไป

เหตุผลหลักที่ฉันจะใช้คือถ้าพารามิเตอร์หมายถึงสิ่งที่แตกต่างกันในตำแหน่งที่ต่างกันหรือหากคุณมีพารามิเตอร์เริ่มต้นสองสามตัว แต่ต้องการรับส่วนที่เหลือจากคอลเล็กชันเช่น

(apply + 1 2 other-number-list)

9

ในกรณีเฉพาะนี้ฉันชอบreduceเพราะมันอ่านง่ายกว่า: เมื่อฉันอ่าน

(reduce + some-numbers)

ฉันรู้ทันทีว่าคุณกำลังเปลี่ยนลำดับให้เป็นค่า

เมื่อapplyฉันต้องพิจารณาว่าจะใช้ฟังก์ชันใด: "อ่ามันเป็น+ฟังก์ชันฉันจึงได้ ... ตัวเลขตัวเดียว" ตรงไปตรงมาน้อยกว่าเล็กน้อย


7

เมื่อใช้ฟังก์ชันง่ายๆเช่น + มันไม่สำคัญว่าคุณจะใช้ฟังก์ชันใด

โดยทั่วไปความคิดreduceคือการดำเนินการสะสม คุณแสดงค่าการสะสมปัจจุบันและค่าใหม่หนึ่งค่าให้กับฟังก์ชันการสะสมของคุณผลลัพธ์ของฟังก์ชันคือค่าสะสมสำหรับการทำซ้ำครั้งถัดไป ดังนั้นการทำซ้ำของคุณมีลักษณะดังนี้:

cum-val[i+1] = F( cum-val[i], input-val[i] )    ; please forgive the java-like syntax!

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

vals = [ val1 val2 val3 ]
(some-fn (vals 0) (vals 1) (vals 2))

เราสามารถพูดได้ว่า:

(apply some-fn vals)

และจะถูกแปลงให้เทียบเท่ากับ:

(some-fn val1 val2 val3)

ดังนั้นการใช้ "ใช้" จึงเหมือนกับการ "ลบวงเล็บ" รอบลำดับ


4

ช้าไปหน่อยในหัวข้อนี้ แต่ฉันได้ทำการทดลองง่ายๆหลังจากอ่านตัวอย่างนี้ นี่เป็นผลมาจากการจำลองของฉันฉันไม่สามารถสรุปอะไรจากการตอบสนองได้ แต่ดูเหมือนว่าจะมีการแคชบางอย่างอยู่ระหว่างลดและใช้

user=> (time (reduce + (range 1e3)))
"Elapsed time: 5.543 msecs"
499500
user=> (time (apply + (range 1e3))) 
"Elapsed time: 5.263 msecs"
499500
user=> (time (apply + (range 1e4)))
"Elapsed time: 19.721 msecs"
49995000
user=> (time (reduce + (range 1e4)))
"Elapsed time: 1.409 msecs"
49995000
user=> (time (reduce + (range 1e5)))
"Elapsed time: 17.524 msecs"
4999950000
user=> (time (apply + (range 1e5)))
"Elapsed time: 11.548 msecs"
4999950000

การดูซอร์สโค้ดของ clojure ช่วยลดการเรียกซ้ำที่ค่อนข้างสะอาดด้วยการลดขนาดภายในไม่พบสิ่งใดในการนำไปใช้ การใช้ Clojure ของ + เพื่อใช้ภายในเรียกใช้การลดซึ่งแคชโดยการจำลองซึ่งดูเหมือนจะอธิบายการเรียกครั้งที่ 4 ใครช่วยชี้แจงสิ่งที่เกิดขึ้นที่นี่ได้บ้าง


ฉันรู้ว่าฉันจะชอบลดทุกเมื่อที่ทำได้ :)
rohit

2
คุณไม่ควรวางrangeสายในtimeแบบฟอร์ม วางไว้ด้านนอกเพื่อขจัดสิ่งรบกวนของโครงสร้างลำดับ ในกรณีของฉันอย่างต่อเนื่องมีประสิทธิภาพดีกว่าreduce apply
Davyzhu

3

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

(apply + 1 2 3 [3 4])
=> 13
(reduce + 1 2 3 [3 4])
ArityException Wrong number of args (5) passed to: core/reduce  clojure.lang.AFn.throwArity (AFn.java:429)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.