เหตุใดสหภาพแรงงานจึงเลือกปฏิบัติที่เกี่ยวข้องกับการโปรแกรมเชิงปฏิบัติ


30

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

มีบางสิ่งที่มีอยู่ในการเขียนโปรแกรมการทำงานที่ทำให้สหภาพแรงงานที่ถูกเลือกปฏิบัติมีประโยชน์มากกว่าพวกเขาจะอยู่ใน OO หรือว่าโดยการบังคับให้ตัวเองวิเคราะห์ปัญหาด้วยวิธีที่ "ดีกว่า" ฉันแค่ปรับมาตรฐานของฉัน รูปแบบ?


ปัญหาการแสดงออกen.wikipedia.org/wiki/Expression_problemอาจเกี่ยวข้อง
xji

มันไม่ได้เป็นการตอบคำถามที่ถูกต้องดังนั้นฉันจะตอบเป็นความคิดเห็นแทน แต่ภาษาซีย์ลอนมีประเภทยูเนี่ยนและดูเหมือนว่าพวกเขากำลังคืบคลานเข้ามาในภาษา OO / แบบผสมอื่น ๆ - TypeScript และ Scala เข้ามาในใจของฉัน นอกจากนี้ภาษา Java enums สามารถใช้เป็นการจัดเรียงของสหภาพที่มีการเลือกปฏิบัติ
Roland Tepp

คำตอบ:


45

สหภาพที่ถูกเลือกปฏิบัตินั้นมีความสัมพันธ์กับการจับคู่รูปแบบซึ่งคุณจะเลือกพฤติกรรมที่แตกต่างกันไปตามแต่ละกรณี แต่รูปแบบนี้เป็นสิ่งที่ตรงกันข้ามกับหลักการ OO บริสุทธิ์

ใน OO บริสุทธิ์ความแตกต่างในพฤติกรรมควรถูกกำหนดโดยประเภท (วัตถุ) ตัวเองและห่อหุ้ม ดังนั้นความเท่าเทียมกันของการจับคู่รูปแบบจะเรียกวิธีการเดียวบนวัตถุนั้นเองซึ่งจากนั้นจะมีการโหลดประเภทย่อยมากเกินไปเพื่อกำหนดพฤติกรรมที่แตกต่างกัน การตรวจสอบประเภทของวัตถุจากภายนอก (ซึ่งเป็นสิ่งที่การจับคู่รูปแบบทำ) ถือว่าเป็น antipattern

ความแตกต่างพื้นฐานคือข้อมูลและพฤติกรรมนั้นแยกจากกันในการเขียนโปรแกรมเชิงฟังก์ชันในขณะที่ข้อมูลและพฤติกรรมนั้นถูกรวมอยู่ใน OO

นี่คือเหตุผลทางประวัติศาสตร์ ภาษาอย่าง C # กำลังพัฒนาจากภาษา OO แบบคลาสสิกไปจนถึงภาษาแบบหลายกระบวนทัศน์โดยรวมคุณสมบัติของฟังก์ชั่นเข้าด้วยกัน


6
ประเภทสหภาพ / ผลรวมที่แบ่งแยกนั้นไม่เหมือนกับต้นไม้มรดกหรือหลายคลาสที่ใช้อินเทอร์เฟซ เป็นประเภทเดียวที่มีค่ามากมาย พวกมันยังไม่ป้องกันการห่อหุ้ม ลูกค้าไม่จำเป็นต้องรู้ว่าคุณมีคุณค่าหลายประเภท พิจารณารายการที่ลิงก์ซึ่งอาจเป็นโหนดว่างเปล่าหรือค่าและการอ้างอิงไปยังโหนดอื่น หากไม่มีประเภทผลรวมคุณจะต้องแฮ็กสหภาพแบบแยกย่อยโดยการมีตัวแปรสำหรับค่าและการอ้างอิง แต่การรักษาวัตถุที่มีการอ้างอิงแบบ null เป็นโหนดว่างเปล่าและทำท่าตัวแปรค่าไม่มีอยู่
Doval

2
โดยทั่วไปแล้วคุณจะต้องใส่ตัวแปรสำหรับค่าที่เป็นไปได้ทั้งหมดในชั้นเดียวรวมทั้งการตั้งค่าสถานะหรือค่า enum เพื่อบอกคุณว่าค่าประเภทนั้นเป็นเช่นไรและเต้นรำไปรอบ ๆ ตัวแปรที่ไม่สอดคล้องกับสิ่งนั้น ชนิด.
Doval

7
@Doval มีการเข้ารหัส "มาตรฐาน" ของชนิดข้อมูลเชิงพีชคณิตในคลาสโดยให้คลาสฐานอินเตอร์เฟส / นามธรรมแสดงชนิดโดยรวมและมีคลาสย่อยสำหรับแต่ละประเภทของกรณี ที่จะจัดการกับการจับคู่แบบคุณมีวิธีการที่จะใช้ฟังก์ชั่นสำหรับแต่ละกรณีดังนั้นสำหรับรายการที่คุณต้องการได้ในอินเตอร์เฟซระดับบนสุดวิธีการList<A> B Match<B>(B nil, Func<A,List<A>,B> cons)ตัวอย่างเช่นนี่เป็นรูปแบบที่ Smalltalk ใช้สำหรับบูลีน นี่เป็นพื้นฐานที่สกาลาจัดการได้ มีการใช้คลาสหลายคลาสเป็นรายละเอียดการใช้งานที่ไม่จำเป็นต้องเปิดเผย
Derek Elkins

3
@Doval: การตรวจสอบอย่างชัดเจนสำหรับการอ้างอิงที่เป็นโมฆะไม่ถือเป็น OO ที่มีลักษณะเหมือนจริง
JacquesB

@JacquesB การตรวจสอบ null เป็นรายละเอียดการใช้งาน สำหรับลูกค้าของรายการที่เชื่อมโยงมักจะมีisEmptyวิธีการตรวจสอบว่าการอ้างอิงถึงโหนดถัดไปเป็นโมฆะ
Doval

35

เมื่อตั้งโปรแกรมใน Pascal และ Ada ก่อนที่จะเรียนรู้การเขียนโปรแกรมใช้งานได้ฉันไม่ได้เชื่อมโยงสหภาพที่แบ่งแยกกับการเขียนโปรแกรมที่ใช้งานได้

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

เนื่องจากการเน้นของ OO กับชนิดและการเน้นสองของการเขียนโปรแกรมฟังก์ชั่นในฟังก์ชั่นภาษาการเขียนโปรแกรมการทำงานมีความสัมพันธ์ตามธรรมชาติสำหรับประเภทสหภาพและเสนอโครงสร้างประโยคเพื่อความสะดวกในการใช้งาน


7

เทคนิคการเขียนโปรแกรมที่จำเป็นตามที่ใช้บ่อยใน OO พึ่งพาบ่อยในสองรูปแบบ:

  1. สำเร็จหรือโยนข้อยกเว้น
  2. กลับnullไปเพื่อระบุ "ไม่มีค่า" หรือล้มเหลว

โดยทั่วไปกระบวนทัศน์การทำงานจะหลีกเลี่ยงทั้งสองอย่างโดยเลือกที่จะคืนค่าชนิดผสมที่ระบุเหตุผลความสำเร็จ / ความล้มเหลวหรือค่า / ไม่มีค่า

สหภาพที่มีการเลือกปฏิบัตินั้นเหมาะสมกับค่าใช้จ่ายสำหรับประเภทสารประกอบเหล่านี้ ตัวอย่างเช่นในอินสแตนซ์แรกคุณอาจกลับมาtrueหรือโครงสร้างข้อมูลบางอย่างที่อธิบายถึงความล้มเหลว ในกรณีที่สองสหภาพที่มีค่าหรือnone, nilฯลฯ กรณีที่สองเป็นเรื่องธรรมดาเพื่อที่ภาษาการทำงานจำนวนมากมี "อาจจะ" หรือ "ตัวเลือก" ประเภทในตัวที่จะเป็นตัวแทนที่ค่า / ยูเนี่ยไม่มี

เมื่อเปลี่ยนเป็นสไตล์การใช้งานด้วยเช่น C # คุณจะพบความต้องการประเภทสารประกอบเหล่านี้อย่างรวดเร็ว void/throwและnullเพียงแค่รู้สึกไม่ถูกต้องกับรหัสดังกล่าว และสหภาพที่มีการแบ่งแยก (DUs) นั้นเหมาะสมกับร่างกฎหมาย ดังนั้นคุณพบว่าตัวเองต้องการพวกเขาเช่นเดียวกับพวกเราหลายคนมี

ข่าวดีก็คือมีห้องสมุดมากมายที่นั่นมีโมเดล DUs เช่น C # (ดูที่ตัวอย่าง Succinc <T> ไลบรารี่ของฉันเอง)


2

โดยทั่วไปแล้วประเภทผลรวมจะมีประโยชน์น้อยกว่าในภาษา OO กระแสหลักเนื่องจากกำลังแก้ไขปัญหาประเภทเดียวกันกับการพิมพ์ย่อย OO วิธีหนึ่งในการดูพวกเขาก็คือพวกเขาทั้งสองจัดการ subtyping แต่ OO คือopenหนึ่งสามารถเพิ่มประเภทย่อยโดยพลการเพื่อประเภทผู้ปกครองและประเภทผลรวมclosedคือหนึ่งที่กำหนดล่วงหน้าประเภทย่อยที่ถูกต้อง

ตอนนี้ภาษา OO มากมายรวม subtyping กับแนวคิดอื่น ๆ เช่น structs ที่สืบทอด, polymorphism, การพิมพ์อ้างอิง ฯลฯ เพื่อให้เป็นประโยชน์โดยทั่วไป ผลที่ตามมาก็คือพวกเขามีแนวโน้มที่จะทำงานมากขึ้นในการตั้งค่า (กับคลาสและตัวสร้างและ whatnot) ดังนั้นจึงไม่ควรใช้กับสิ่งต่าง ๆ เช่นResults และOptions เป็นต้นจนกระทั่งการพิมพ์ทั่วไปกลายเป็นเรื่องธรรมดา

ฉันยังบอกด้วยว่าการให้ความสำคัญกับความสัมพันธ์ในโลกแห่งความเป็นจริงที่คนส่วนใหญ่เรียนรู้เมื่อพวกเขาเริ่มเขียนโปรแกรม OO เช่น Dog isa Animal หมายความว่า Integer isa Results หรือ Error isa Result ดูเหมือนคนต่างด้าวเล็กน้อย แม้ว่าความคิดจะคล้ายกันมาก

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


BTW: สกาล่าได้ปิดการรับมรดก sealedหมายถึง "สามารถขยายได้ในหน่วยการคอมไพล์เดียวกัน" เท่านั้นซึ่งช่วยให้คุณปิดชุดของคลาสย่อยในเวลาออกแบบ
Jörg W Mittag

-3

Swift ใช้สหภาพอย่างมีความสุขแยกแยะอย่างมีความสุขยกเว้นมันเรียกพวกเขาว่า "enums" Enums เป็นหนึ่งในห้าประเภทพื้นฐานของวัตถุใน Swift ซึ่ง ได้แก่ class, struct, enum, tuple และการปิด ตัวเลือก Swift เป็นจำนวนเงินที่แยกออกจากสหภาพและเป็นสิ่งจำเป็นอย่างยิ่งสำหรับรหัส Swift

หลักฐานที่ว่า "สหภาพเลือกปฏิบัติมีความเกี่ยวข้องกับการเขียนโปรแกรมฟังก์ชั่น" จึงผิด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.