ชุมชน C++ ถกเถียงว่าการออกแบบ std::adjacent_difference เป็นความผิดพลาดหรือไม่

ทีมชุมชน BigGo
ชุมชน C++ ถกเถียงว่าการออกแบบ std::adjacent_difference เป็นความผิดพลาดหรือไม่

ชุมชนโปรแกรมเมอร์ 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