ชุมชนนักเขียนโปรแกรม Haskell กำลังมีส่วนร่วมในการอภิปรายอย่างเข้มข้นเกี่ยวกับว่า newtypes ให้ความปลอดภัยของประเภทข้อมูลตามที่สัญญาไว้จริงหรือไม่ หรือเป็นเพียงแบบแผนการตั้งชื่อที่ซับซ้อนซึ่งสามารถหลีกเลี่ยงได้ง่าย การถกเถียงนี้เกิดขึ้นจากบทความล่าสุดที่โต้แย้งว่า newtypes ให้การรับประกันความปลอดภัยที่อ่อนแอกว่าที่นักพัฒนาหลายคนเชื่อ
ความแตกต่างหลักของความปลอดภัย
การอภิปรายมุ่งเน้นไปที่แนวทางความปลอดภัยของประเภทข้อมูลที่แตกต่างกันโดยพื้นฐานสองแบบ: ความปลอดภัยแบบภายในและภายนอก ความปลอดภัยแบบภายในหมายความว่าระบบประเภทข้อมูลเองป้องกันไม่ให้สถานะที่ไม่ถูกต้องถูกแสดง - ไม่มีทางที่จะสร้างค่าที่ผิดกฎหมายได้เลย ในทางตรงกันข้าม ความปลอดภัยแบบภายนอกอาศัยวินัยของโปรแกรมเมอร์และขอบเขตของโมดูลในการรักษาข้อจำกัดต่างๆ
เมื่อนักพัฒนาใช้ algebraic data types ที่มี constructor ชัดเจนเช่น One | Two | Three | Four
คอมไพเลอร์สามารถตรวจสอบได้ว่าทุกกรณีได้รับการจัดการแล้ว แต่กับ newtypes ที่ห่อหุ้มประเภทพื้นฐานเช่นจำนวนเต็ม ฟังก์ชันมักจำเป็นต้องมีกรณี catch-all ที่มีข้อผิดพลาดรันไทม์ ซึ่งสร้างจุดล้มเหลวที่อาจเกิดขึ้นซึ่งจะปรากฏขึ้นระหว่างการทำงานมากกว่าการคอมไพล์
หมายเหตุ: Algebraic data types ช่วยให้คุณกำหนดประเภทโดยการรวมประเภทอื่นๆ เช่น การสร้างประเภทที่สามารถเป็นได้เพียง One, Two, Three หรือ Four เท่านั้น
การเปรียบเทียบความปลอดภัยแบบ Intrinsic กับ Extrinsic
แนวทาง | กลไกความปลอดภัย | การรับประกันเวลา Compile | ข้อผิดพลาดเวลา Runtime | ภาระการบำรุงรักษา |
---|---|---|---|---|
Intrinsic ( ADTs ) | การบังคับใช้ระบบ Type | ครอบคลุมกรณีทั้งหมด | ถูกกำจัดแล้ว | ต่ำ |
Extrinsic ( Newtypes ) | ขอบเขต Module | จำกัด | เป็นไปได้ | สูง |
ปัญหาขอบเขตโมดูล
สมาชิกในชุมชนชี้ให้เห็นว่า newtypes สร้างสิ่งที่เรียกว่าปัญหาโค้ดที่เชื่อถือได้ แม้ว่าการซ่อน constructor ในโมดูลจะให้การป้องกันบางประการ แต่ก็ต้องการความระมัดระวังอย่างต่อเนื่องเพื่อให้แน่ใจว่าไม่มีโค้ดภายในโมดูลนั้นสร้างค่าที่ไม่ถูกต้องโดยไม่ตั้งใจ นักพัฒนาคนหนึ่งสังเกตว่าแนวทางนี้ต้องการการตรวจสอบอย่างระมัดระวังในทุกการเปลี่ยนแปลงเพื่อป้องกันไม่ให้ข้อบกพร่องที่ละเอียดอ่อนเข้ามา
ความท้าทายจะชัดเจนมากขึ้นเมื่อต้องจัดการกับ type class instances หาก newtype จำเป็นต้องใช้งานอินเทอร์เฟซทั่วไปเช่น Monoid
นักพัฒนาอาจสร้างเส้นทางที่หลีกเลี่ยงกลไกความปลอดภัยที่ตั้งใจไว้โดยไม่ตั้งใจ สิ่งนี้สร้างภาระการบำรุงรักษาและช่องโหว่ด้านความปลอดภัยที่อาจเกิดขึ้น ซึ่งไม่มีอยู่ในแนวทางที่เป็นภายในอย่างแท้จริง
หมายเหตุ: Monoid เป็นแนวคิดทางคณิตศาสตร์ในการเขียนโปรแกรมที่แสดงถึงประเภทที่มีการดำเนินการแบบเชื่อมโยงและองค์ประกอบเอกลักษณ์ เช่น การบวกกับศูนย์
การประยุกต์ใช้จริงและทางออก
แม้จะมีข้อจำกัดเหล่านี้ ชุมชนยอมรับว่า newtypes มีจุดประสงค์ที่สำคัญในทางปฏิบัตินอกเหนือจากความปลอดภัย มีประโยชน์อย่างยิ่งสำหรับการแก้ไขข้อจำกัดของ Haskell ที่มี type class instance เพียงหนึ่งตัวต่อประเภท ช่วยให้นักพัฒนาสามารถให้การใช้งานหลายแบบสำหรับประเภทพื้นฐานเดียวกัน
นักพัฒนาบางคนโต้แย้งว่าไม่ควรมองข้ามประโยชน์ด้านการตั้งชื่อและเอกสารของ newtypes แม้ว่าจะไม่ให้ความปลอดภัยที่กันกระสุน แต่ก็ทำให้โค้ดอ่านง่ายขึ้นและช่วยป้องกันข้อผิดพลาดง่ายๆ เช่น การสับสน user ID กับ product ID มุมมองนี้ชี้ให้เห็นว่าความปลอดภัยของประเภทข้อมูลที่สมบูรณ์แบบไม่จำเป็นเสมอไปหากทางเลือกให้ประโยชน์ในทางปฏิบัติที่เพียงพอ
การใช้งาน Newtype นอกเหนือจากความปลอดภัย
- การแก้ไขปัญหา Type Class: การจัดเตรียม instance หลายตัวสำหรับ type พื้นฐานเดียวกัน (เช่น
Sum
และProduct
สำหรับตัวเลข) - การสร้างตราสินค้าใน Namespace: การสร้าง type parameter ที่แตกต่างกันสำหรับ generic function
- การจัดทำเอกสารโค้ด: ทำให้ function signature อธิบายตัวเองได้ชัดเจนยิ่งขึ้น
- ความปลอดภัยในการ Refactoring: ป้องกันการผสมค่าที่แตกต่างกันทางความหมายแต่มีโครงสร้างเหมือนกันโดยไม่ตั้งใจ
แนวทางทางเลือกและการเปรียบเทียบภาษา
การอภิปรายได้ขยายไปถึงการเปรียบเทียบกับภาษาการเขียนโปรแกรมอื่นๆ นักพัฒนา Rust กล่าวถึงการใช้ newtypes กับการใช้งาน Deref
เพื่อให้ได้ความปลอดภัยของประเภทข้อมูลโดยไม่สูญเสียความสะดวกในการใช้งาน ในขณะเดียวกัน ผู้สนับสนุน structural typing โต้แย้งว่าการเน้นไปที่ชื่อมากเกินไปทำให้พลาดภาพรวมใหญ่ของการแปลงข้อมูล
ประโยชน์ที่ใหญ่ที่สุดจากภาษาที่มีการกำหนดประเภทมาจากการใช้เทคนิคพื้นฐานเหล่านี้
สมาชิกในชุมชนบางคนแนะนำว่าทางออกที่แท้จริงอาจเกี่ยวข้องกับการปรับปรุงระดับภาษา เช่น การสนับสนุนที่ดีกว่าสำหรับประเภทที่มีข้อจำกัดหรือระบบโมดูลที่ซับซ้อนมากขึ้นซึ่งสามารถบังคับใช้ข้อจำกัดได้อย่างน่าเชื่อถือมากขึ้น
ปรัชญาระบบประเภทข้อมูลในวงกว้าง
การถกเถียงนี้สะท้อนถึงความแตกแยกทางปรัชญาที่ลึกซึ้งเกี่ยวกับสิ่งที่ระบบประเภทข้อมูลควรทำให้สำเร็จ นักพัฒนาบางคนให้ความสำคัญกับความเข้มงวดทางคณิตศาสตร์และการรับประกันเวลาคอมไพล์ ในขณะที่คนอื่นๆ ให้ความสำคัญกับประโยชน์ในทางปฏิบัติเช่น การจัดระเบียบโค้ดที่ดีขึ้นและการลดภาระทางปัญญาระหว่างการพัฒนา
ฉันทามติดูเหมือนจะเป็นว่า newtypes อยู่ในตำแหน่งกลางๆ - มากกว่าแค่แบบแผนการตั้งชื่อแต่น้อยกว่าความปลอดภัยของประเภทข้อมูลที่แท้จริง สำหรับแอปพลิเคชันในโลกจริงหลายๆ ตัว ตำแหน่งกลางนี้ให้การป้องกันที่เพียงพอในขณะที่ยังคงใช้งานได้จริงในการใช้งานและบำรุงรักษา อย่างไรก็ตาม นักพัฒนาที่ทำงานกับระบบที่สำคัญอาจต้องลงทุนในแนวทางที่แข็งแกร่งมากขึ้น แม้ว่าจะต้องการความซับซ้อนเพิ่มเติม
อ้างอิง: Names are not type safety