ชุมชนโปรแกรมเมอร์ C++ กำลังมีส่วนร่วมในการอภิปรายอย่างร้อนแรงเกี่ยวกับหนึ่งในการตัดสินใจออกแบบที่ถกเถียงกันมากที่สุดของ Standard Template Library (STL) ศูนย์กลางของการถกเถียงคือ std::adjacent_difference
ซึ่งเป็นอัลกอริทึมที่คำนวณความแตกต่างระหว่างองค์ประกอบที่อยู่ติดกันในลำดับ แต่มีจุดแปลกประหลาดที่ทำให้นักพัฒนาหงุดหงิดมาหลายทศวรรษ
ปัญหาหลักเรื่องความเข้ากันได้ของประเภทข้อมูล
ปัญหาหลักเกิดจากวิธีที่ std::adjacent_difference
จัดการกับผลลัพธ์ ซึ่งแตกต่างจากที่หลายคนคาดหวัง อัลกอริทึมนี้ไม่ได้แค่คำนวณความแตกต่าง แต่ยังคัดลอกองค์ประกอบแรกของลำดับอินพุตไปยังเอาต์พุตโดยไม่เปลี่ยนแปลง การเลือกออกแบบที่ดูเหมือนไม่เป็นอันตรายนี้สร้างปัญหาใหญ่เมื่อทำงานกับประเภทข้อมูลที่แตกต่างกัน
สมาชิกในชุมชนได้เน้นย้ำว่าสิ่งนี้บังคับให้ประเภทเอาต์พุตต้องตรงกับประเภทอินพุต ซึ่งมักจะไม่สมเหตุสมผลทางคณิตศาสตร์ เมื่อคุณลบ timestamp สองค่า คุณจะได้ duration ไม่ใช่ timestamp อีกตัว เมื่อคุณลบจำนวนเต็มบวกสองตัว คุณอาจต้องใช้จำนวนเต็มที่มีเครื่องหมายเพื่อจัดการผลลัพธ์ที่เป็นลบ การออกแบบปัจจุบันทำให้การดำเนินการทั่วไปเหล่านี้เป็นไปไม่ได้หรือยากที่จะใช้งาน
ปัญหาความเข้ากันได้ของประเภทข้อมูล
- Timestamps:
timestamp - timestamp = duration
(ประเภทข้อมูลที่แตกต่างกัน) - จำนวนเต็มบวก: อาจต้องการผลลัพธ์เป็นจำนวนที่มีเครื่องหมายสำหรับผลต่างที่เป็นลบ
- ประเภทข้อมูลกำหนดเอง: การดำเนินการหาผลต่างมักจะสร้างประเภทผลลัพธ์ที่แตกต่างกัน
- ข้อจำกัดปัจจุบัน: ผลลัพธ์ต้องตรงกับประเภทข้อมูลนำเข้าใน
std::adjacent_difference
เหตุผลทางคณิตศาสตร์เบื้องหลังการออกแบบ
Alex Stepanov ผู้สร้าง STL ไม่ได้เลือกแบบนี้โดยบังเอิญ การออกแบบนี้ทำให้แน่ใจว่า std::adjacent_difference
และ std::partial_sum
ทำงานเป็นตรงข้ามทางคณิตศาสตร์ของกันและกัน สิ่งนี้สร้างความสมมาตรที่สวยงาม ซึ่งคุณสามารถคำนวณความแตกต่างจากลำดับ จากนั้นใช้ partial sum เพื่อสร้างข้อมูลต้นฉบับใหม่ได้อย่างสมบูรณ์
ความเชื่อมโยงนี้ลึกกว่าความสะดวกในการเขียนโปรแกรม การออกแบบสะท้อนแนวคิดจากแคลคูลัส ซึ่งอนุพันธ์และอินทิกรัลมีความสัมพันธ์แบบผกผัน ในคณิตศาสตร์แบบไม่ต่อเนื่อง adjacent difference ทำหน้าที่เป็นอนุพันธ์ ในขณะที่ partial sum ทำหน้าที่เป็นอินทิกรัล องค์ประกอบแรกที่ถูกเก็บไว้ทำหน้าที่เหมือนค่าคงที่ของการอินทิเกรตที่ปรากฏเมื่อคำนวณอินทิกรัลไม่จำกัด
แนวทางทางเลือกและแนวทางแก้ไข
ชุมชนโปรแกรมเมอร์ได้เสนอแนวทางแก้ไขหลายประการเพื่อจัดการกับข้อจำกัด ข้อเสนอแนะหนึ่งเกี่ยวข้องกับการทำให้ partial_sum
รับพารามิเตอร์ accumulator เริ่มต้น ซึ่งจะคืนความสมมาตรในขณะที่อนุญาตให้ใช้ประเภทอินพุตและเอาต์พุตที่แตกต่างกัน แนวทางนี้จะแก้ปัญหาประเภทข้อมูลไม่ตรงกันในขณะที่รักษาความสวยงามทางคณิตศาสตร์ที่ Stepanov ตั้งใจไว้
ฉันคิดว่ามันเป็นเพราะ C++ ไม่มีแนวคิดทั่วไปของ 'ศูนย์' มิฉะนั้นคนเราสามารถนิยาม adjacent_difference(v) องค์ประกอบแรกเป็น v(1)- zero<typeof(v)> และมันจะมีเสถียรภาพของประเภท
นักพัฒนาบางคนได้รับแรงบันดาลใจจากภาษาโปรแกรมอื่น ๆ ภาษาโปรแกรม q เสนอฟังก์ชัน deltas
ที่บรรลุคุณสมบัติทางคณิตศาสตร์ที่คล้ายกันในขณะที่หลีกเลี่ยงปัญหาความเข้ากันได้ของประเภท แทนที่จะคัดลอกองค์ประกอบแรกโดยตรง มันจะเพิ่มค่าศูนย์ไว้ข้างหน้าก่อนคำนวณความแตกต่าง รักษาความสัมพันธ์แบบผกผันกับ partial sum โดยไม่บังคับข้อจำกัดของประเภท
การเปรียบเทียบอัลกอริทึม STL หลัก
อัลกอริทึม | ประเภทข้อมูลนำเข้า | ประเภทข้อมูลส่งออก | รักษาองค์ประกอบแรก |
---|---|---|---|
std::adjacent_difference |
T | T (บังคับ) | ใช่ |
std::partial_sum |
T | T | ไม่ |
ฟังก์ชัน deltas ของภาษา q |
T | T | ใช่ (เป็นศูนย์) |
ผลกระทบที่กว้างขึ้นต่อ C++ สมัยใหม่
การถกเถียงนี้สะท้อนคำถามที่ใหญ่กว่าเกี่ยวกับการออกแบบ API และความเข้ากันได้แบบย้อนหลังในภาษาโปรแกรม แม้ว่า C++ สมัยใหม่จะได้แนะนำฟีเจอร์อย่าง T{}
สำหรับการเริ่มต้นค่าศูนย์ แต่ std::adjacent_difference
ต้นฉบับยังคงไม่เปลี่ยนแปลงเพื่อเหตุผลด้านความเข้ากันได้ นักพัฒนาหลายคนต้องหันไปเขียนลูปแบบกำหนดเองหรือฟังก์ชัน wrapper เพื่อแก้ไขข้อจำกัด
การอภิปรายยังสัมผัสกับการตัดสินใจออกแบบ STL ที่ถกเถียงกันอื่น ๆ โดยสมาชิกในชุมชนเปรียบเทียบกับ std::vector<bool>
ซึ่งเป็นอีกหนึ่ง specialization ที่ถูกวิพากษ์วิจารณ์อย่างกว้างขวางที่ทำลายพฤติกรรม container ที่คาดหวัง ตัวอย่างเหล่านี้เน้นย้ำถึงความท้าทายในการสร้างสมดุลระหว่างความสวยงามทางคณิตศาสตร์กับความใช้งานได้จริงในการออกแบบไลบรารี
การถกเถียงที่กำลังดำเนินอยู่แสดงให้เห็นว่าการตัดสินใจออกแบบที่ทำขึ้นเมื่อหลายทศวรรษก่อนยังคงมีอิทธิพลต่อแนวทางปฏิบัติการเขียนโปรแกรมสมัยใหม่ ในขณะที่การเลือกของ Stepanov สร้างความงามทางคณิตศาสตร์ผ่านความสัมพันธ์แบบผกผันระหว่าง adjacent difference และ partial sum แต่ก็แนะนำข้อจำกัดเชิงปฏิบัติที่นักพัฒนายังคงต้องต่อสู้อยู่ในปัจจุบัน ว่าสิ่งนี้แสดงถึงข้อบกพร่องในการออกแบบพื้นฐานหรือการแลกเปลี่ยนที่ยอมรับได้เพื่อความสวยงามทางคณิตศาสตร์ยังคงเป็นเรื่องของมุมมองในชุมชน C++
อ้างอิง: Stepanov's biggest blunder