การจัดส่งหลายรายการ
| โพลีมอร์ฟิซึม |
|---|
| โพลีมอร์ฟิซึมเฉพาะกิจ |
| โพลีมอร์ฟิซึมแบบพาราเมตริก |
| การกำหนดประเภทย่อย |
การเรียกใช้ฟังก์ชันหลายรายการหรือเมธอดหลายรายการเป็นคุณลักษณะของภาษาโปรแกรม บางภาษา ที่ฟังก์ชันหรือเมธอดสามารถเรียกใช้แบบไดนามิกได้โดยอิงตาม ประเภท รันไทม์ (ไดนามิก) หรือในกรณีทั่วไป คุณลักษณะอื่น ๆ ของ อาร์กิวเมนต์มากกว่าหนึ่งรายการ[ 1 ] นี่เป็นการขยายความของ โพลีมอร์ ฟิซึมแบบ เรียกใช้ฟังก์ชัน รายการเดียว ซึ่งการเรียกใช้ฟังก์ชันหรือเมธอดจะถูกเรียกใช้แบบไดนามิกโดยอิงตามประเภทที่ได้มาของวัตถุที่เรียกใช้เมธอด การเรียกใช้ฟังก์ชันหลายรายการ จะกำหนดเส้นทางการเรียกใช้แบบไดนามิกไปยังฟังก์ชันหรือเมธอดที่ใช้งานโดยใช้ลักษณะที่รวมกันของอาร์กิวเมนต์หนึ่งรายการขึ้นไป
ทำความเข้าใจเกี่ยวกับการจัดส่ง
โดยทั่วไปแล้ว นักพัฒนาซอฟต์แวร์คอมพิวเตอร์จะจัดระเบียบซอร์สโค้ดเป็นบล็อกที่มีชื่อเรียกต่างๆ กัน เช่นซับรูทีนโปรซีเจอร์ ซับโปรแกรม ฟังก์ชัน หรือเมธอด โค้ดในฟังก์ชันจะถูกเรียกใช้งานโดยการเรียกใช้ ฟังก์ชัน นั้น – คือการเรียกใช้โค้ดที่อ้างอิงถึงชื่อ ของ ฟังก์ชันนั้น การกระทำนี้จะถ่ายโอนการควบคุมไปยังฟังก์ชันที่ถูกเรียกชั่วคราว เมื่อการทำงานของฟังก์ชันเสร็จสิ้น การควบคุมจะถูกส่งกลับไปยังคำสั่งในฟังก์ชันที่เรียกใช้ซึ่งอยู่ถัดจากการอ้างอิงนั้น
โดยปกติแล้ว ชื่อฟังก์ชันมักถูกเลือกให้สื่อถึงวัตถุประสงค์ของฟังก์ชันนั้น ๆ บางครั้งอาจเป็นที่ต้องการที่จะตั้งชื่อฟังก์ชันหลาย ๆ ฟังก์ชันให้เหมือนกัน ซึ่งมักเป็นเพราะฟังก์ชันเหล่านั้นทำงานคล้ายกันในเชิงแนวคิด แต่ทำงานกับข้อมูลอินพุตที่แตกต่างกัน ในกรณีเช่นนี้ การอ้างอิงชื่อที่ตำแหน่งการเรียกใช้ ฟังก์ชัน นั้นไม่เพียงพอสำหรับการระบุบล็อกโค้ดที่จะถูกเรียกใช้ แต่จำนวนและประเภทของอาร์กิวเมนต์ในการเรียกใช้ฟังก์ชันจะถูกนำมาใช้เพื่อเลือกการใช้งานฟังก์ชันจากหลาย ๆ แบบแทน
ในภาษา การเขียนโปรแกรมเชิงวัตถุแบบดั้งเดิม เช่น ภาษาที่มีการเรียกใช้เมธอด แบบซิงเกิลดิสแพต ช์ เมื่อเรียกใช้เมธอด (เช่นการส่งข้อความในSmalltalkหรือการเรียกใช้ฟังก์ชันสมาชิกในC++ ) อาร์กิวเมนต์ตัวใดตัวหนึ่งจะถูกจัดการเป็นพิเศษและใช้เพื่อกำหนดว่าควรใช้เมธอดคลาสใด (ซึ่งอาจมีหลายคลาส) ที่มีชื่อเดียวกัน ในหลายภาษา อาร์กิวเมนต์พิเศษจะระบุไว้ในไวยากรณ์ ตัวอย่างเช่น ภาษาการเขียนโปรแกรมหลายภาษาจะวางอาร์กิวเมนต์พิเศษไว้ก่อนจุดเมื่อเรียกใช้เมธอด: ดังนั้นจะทำให้เกิดเสียงคำราม ในขณะที่จะทำให้เกิดเสียงแหลมspecial.method(other, arguments, here)lion.sound()sparrow.sound()
ในทางตรงกันข้าม ในภาษาโปรแกรมที่มีการเรียกใช้ฟังก์ชันหลายแบบ (multiple dispatch) เมธอดที่ถูกเลือกจะเป็นเพียงเมธอดที่มีอาร์กิวเมนต์ตรงกับจำนวนและประเภทของการเรียกใช้ฟังก์ชันนั้น ๆ ไม่มีอาร์กิวเมนต์พิเศษ ใดที่ เป็นเจ้าของฟังก์ชัน/เมธอดที่จะถูกเรียกใช้ในการเรียกใช้ครั้งนั้น ๆ
การเรียกใช้ฟังก์ชัน หลายตัวพร้อมกัน (Multiple dispatch) ควรแยกแยะออกจากการโอเวอร์โหลดฟังก์ชัน (Function overloading ) ซึ่งข้อมูลการกำหนดประเภทแบบคงที่ เช่น ประเภทที่ประกาศหรืออนุมานของเทอม (หรือประเภทพื้นฐานในภาษาที่มีการกำหนดประเภทย่อย) จะถูกใช้เพื่อกำหนดว่าควรใช้ความเป็นไปได้ใดในหลายๆ ความเป็นไปได้ ณ จุดที่เรียกใช้ และการกำหนดนั้นจะทำในระหว่างการคอมไพล์หรือการเชื่อมโยง (หรือเวลาอื่นก่อนที่โปรแกรมจะเริ่มทำงาน) และจะไม่เปลี่ยนแปลงหลังจากนั้นสำหรับการใช้งานหรือการทำงานของโปรแกรมในแต่ละครั้ง ภาษาโปรแกรมหลายภาษา เช่น C++ มีการโอเวอร์โหลดฟังก์ชันที่แข็งแกร่ง แต่ไม่มีการเรียกใช้ฟังก์ชันหลายตัวพร้อมกันแบบไดนามิก (C++ อนุญาตให้เรียกใช้ฟังก์ชันตัวเดียวพร้อมกันแบบไดนามิกได้เฉพาะผ่านการใช้ฟังก์ชันเสมือนเท่านั้น)
ประเภทข้อมูล
เมื่อทำงานกับภาษาโปรแกรมที่สามารถแยกแยะชนิดข้อมูล ได้ ในระหว่างการคอมไพล์การเลือกใช้ฟังก์ชันทางเลือกจึงเกิดขึ้นได้ในขั้นตอนนี้ การสร้างฟังก์ชันทางเลือกดังกล่าวเพื่อให้เลือกใช้ในระหว่างการคอมไพล์นั้น โดยทั่วไปเรียกว่าการโอเวอร์โหลดฟังก์ชัน (overloading)
ในภาษาโปรแกรมที่เลื่อนการระบุชนิดข้อมูลไปจนถึงเวลาทำงาน (เช่นการผูกแบบล่าช้า ) การเลือกฟังก์ชันทางเลือกจะต้องเกิดขึ้นในเวลานั้น โดยอิงจากชนิดของอาร์กิวเมนต์ของฟังก์ชันที่กำหนดแบบไดนามิก ฟังก์ชันที่มีการเลือกการใช้งานทางเลือกในลักษณะนี้โดยทั่วไปเรียกว่ามัลติเมธอด
การเรียกใช้ฟังก์ชันแบบไดนามิกนั้นมีค่าใช้จ่ายด้านเวลาในการรันไทม์อยู่บ้าง ในบางภาษาความแตกต่างระหว่างการโอเวอร์โหลดและการเรียกใช้ฟังก์ชันหลายเมธอดอาจไม่ชัดเจน โดยคอมไพเลอร์จะเป็นผู้กำหนดว่าสามารถใช้การเลือกในขั้นตอนการคอมไพล์กับการเรียกใช้ฟังก์ชันที่กำหนดได้หรือไม่ หรือจำเป็นต้องใช้การเรียกใช้ฟังก์ชันแบบไดนามิกที่ช้ากว่าในเวลารันไทม์
ปัญหา
มีปัญหาที่ทราบกันดีอยู่หลายประการเกี่ยวกับการเรียกใช้ฟังก์ชันแบบไดนามิก ทั้งแบบเรียกใช้ฟังก์ชันเดียวและหลายฟังก์ชัน ในขณะที่ปัญหาเหล่านี้หลายอย่างได้รับการแก้ไขแล้วสำหรับการเรียกใช้ฟังก์ชันแบบเดียว ซึ่งเป็นคุณสมบัติมาตรฐานในภาษาการเขียนโปรแกรมเชิงวัตถุมานานหลายทศวรรษ ปัญหาเหล่านี้จะซับซ้อนมากขึ้นในกรณีของการเรียกใช้ฟังก์ชันหลายฟังก์ชัน
การแสดงออกและความเป็นโมดูลาร์
ในภาษาโปรแกรมยอดนิยมส่วนใหญ่ โค้ดต้นฉบับจะถูกส่งและใช้งานในรูปแบบของกลุ่มฟังก์ชันการทำงาน ซึ่งเราจะเรียกว่าแพ็กเกจ (package ) โดยคำศัพท์ที่ใช้เรียกแนวคิดนี้อาจแตกต่างกันไปในแต่ละภาษา แต่ละแพ็กเกจอาจประกอบด้วยคำจำกัดความของประเภท ค่า และฟังก์ชันหลายรายการ แพ็กเกจมักจะถูกคอมไพล์แยกกันในภาษาที่มีขั้นตอนการคอมไพล์ และอาจมีความสัมพันธ์แบบพึ่งพาที่ไม่เป็นวัฏจักร โปรแกรมที่สมบูรณ์คือชุดของแพ็กเกจ โดยมีแพ็กเกจหลักที่อาจขึ้นอยู่กับแพ็กเกจอื่น ๆ หลายแพ็กเกจ และโปรแกรมทั้งหมดประกอบด้วยความสัมพันธ์แบบพึ่งพาที่เชื่อมโยงกัน (transitive closure)
ปัญหา ที่เรียกว่า " ปัญหาการแสดงออก"เกี่ยวข้องกับความสามารถของโค้ดในแพ็กเกจที่ขึ้นอยู่ต่อในการขยายพฤติกรรม (ฟังก์ชันหรือชนิดข้อมูล) ที่กำหนดไว้ในแพ็กเกจพื้นฐานจากภายในแพ็กเกจที่รวมอยู่ โดยไม่ต้องแก้ไขซอร์สโค้ดของแพ็กเกจพื้นฐาน ภาษาโปรแกรมเชิงวัตถุแบบ single-dispatch ดั้งเดิมทำให้การเพิ่มชนิดข้อมูลใหม่เป็นเรื่องง่าย แต่ไม่ใช่ฟังก์ชันใหม่ ภาษาโปรแกรมเชิงฟังก์ชันแบบดั้งเดิมมักจะมีผลตรงกันข้าม และ multiple dispatch หากนำไปใช้อย่างถูกต้อง จะช่วยให้ทำได้ทั้งสองอย่าง การนำ multiple dispatch ไปใช้ควรมีคุณสมบัติดังต่อไปนี้:
- สามารถกำหนด "กรณี" ต่างๆ ของมัลติเมธอดจากภายในแพ็กเกจต่างๆ ได้โดยไม่ต้องแก้ไขซอร์สโค้ดของแพ็กเกจหลัก
- การเพิ่มแพ็กเกจอื่นเข้าไปในโปรแกรมไม่ควรเปลี่ยนแปลงพฤติกรรมของการเรียกใช้เมธอดหลายตัวที่กำหนดไว้ หากการเรียกใช้นั้นไม่ได้ใช้ชนิดข้อมูลใดๆ ที่กำหนดไว้ในแพ็กเกจนั้น
- ในทางกลับกัน หากมีการกำหนดชนิดข้อมูลในแพ็กเกจหนึ่ง และมีการกำหนดส่วนขยายแบบหลายเมธอดที่ใช้ชนิดข้อมูลนั้นในแพ็กเกจเดียวกัน และมีการส่งค่าของชนิดข้อมูลนั้น (ผ่านการอ้างอิงชนิดข้อมูลพื้นฐานหรือเข้าไปในฟังก์ชันทั่วไป) ไปยังแพ็กเกจอื่นที่ไม่มีการพึ่งพาแพ็กเกจนั้น และจากนั้นเรียกใช้แบบหลายเมธอดโดยใช้ค่านั้นเป็นอาร์กิวเมนต์ กรณีแบบหลายเมธอดที่กำหนดไว้ในแพ็กเกจที่รวมชนิดข้อมูลนั้นควรถูกนำมาใช้ กล่าวอีกนัยหนึ่งคือ ภายในโปรแกรมเดียวกัน แบบหลายเมธอดเดียวกันที่เรียกใช้ด้วยชุดอาร์กิวเมนต์เดียวกันควรได้ผลลัพธ์เป็นการใช้งานเดียวกัน โดยไม่คำนึงถึงตำแหน่งของไซต์การเรียกใช้ และไม่ว่าคำจำกัดความที่กำหนดนั้นจะ "อยู่ในขอบเขต" หรือ "มองเห็นได้" ณ จุดที่เรียกใช้เมธอดหรือไม่ก็ตาม
ความคลุมเครือ
โดยทั่วไปแล้ว เป็นที่พึงปรารถนาว่าสำหรับการเรียกใช้เมธอดหลายตัวใดๆ ก็ตาม ควรมีตัวเลือก "ที่ดีที่สุด" เพียงหนึ่งเดียวในบรรดากรณีการใช้งานของเมธอดหลายตัวนั้น และ/หรือ หากไม่มีตัวเลือกที่ดีที่สุด ก็ควรได้รับการแก้ไขในลักษณะที่คาดการณ์ได้และแน่นอน รวมถึงความล้มเหลวด้วย พฤติกรรมที่ไม่แน่นอนนั้นไม่เป็นที่พึงปรารถนา สมมติว่ามีชุดของประเภทที่มีความสัมพันธ์แบบ subtyping ที่ไม่วนซ้ำ เราสามารถกำหนดได้ว่าการใช้งานเมธอดหลายตัวหนึ่งนั้น "ดีกว่า" (เฉพาะเจาะจงกว่า) หากอาร์กิวเมนต์ที่ส่งแบบไดนามิกทั้งหมดในตัวแรกเป็น subtype ของอาร์กิวเมนต์ที่ส่งแบบไดนามิกทั้งหมดที่ระบุในตัวที่สอง และอย่างน้อยหนึ่งตัวเป็น subtype ที่เข้มงวด ด้วยการส่งแบบเดี่ยวและในกรณีที่ไม่มีการสืบทอดแบบหลายทางเงื่อนไขนี้จะได้รับการตอบสนองอย่างง่ายดาย แต่ด้วยการส่งแบบหลายทาง เป็นไปได้ที่ตัวเลือกสองตัวขึ้นไปจะตรงตามรายการอาร์กิวเมนต์จริงที่กำหนด แต่ไม่มีตัวใดเฉพาะเจาะจงกว่าอีกตัวหนึ่ง (อาร์กิวเมนต์แบบไดนามิกตัวหนึ่งเป็น subtype ในกรณีหนึ่ง อีกตัวหนึ่งเป็น subtype ในอีกกรณีหนึ่ง) โดยเฉพาะอย่างยิ่ง กรณีที่มีแพ็กเกจสองแพ็กเกจที่แตกต่างกัน ซึ่งต่างไม่ขึ้นกับแพ็กเกจอื่น ต่างก็ขยายเมธอดแบบหลายพารามิเตอร์ด้วยการใช้งานที่เกี่ยวข้องกับประเภทของแต่ละแพ็กเกจ จากนั้นแพ็กเกจที่สามซึ่งรวมทั้งสองแพ็กเกจ (อาจจะโดยอ้อม) ก็เรียกใช้เมธอดแบบหลายพารามิเตอร์นั้นโดยใช้พารามิเตอร์จากทั้งสองแพ็กเกจ
แนวทางแก้ไขที่เป็นไปได้ ได้แก่:
- ถือว่าการเรียกใช้ที่ไม่ชัดเจนใดๆ เป็นข้อผิดพลาด ซึ่งอาจตรวจพบได้ในระหว่างการคอมไพล์ (หรือก่อนการใช้งานจริง) แต่ก็อาจตรวจไม่พบจนกว่าจะถึงเวลาทำงานและทำให้เกิดข้อผิดพลาดขณะรันไทม์
- การจัดลำดับข้อโต้แย้ง เช่น กรณีที่มีข้อโต้แย้งแรกที่เฉพาะเจาะจงที่สุดจะถูกเลือก และข้อโต้แย้งถัดไปจะไม่ถูกนำมาพิจารณาในการแก้ไขความกำกวม เว้นแต่ว่าข้อโต้แย้งแรกไม่เพียงพอที่จะแก้ไขปัญหาได้
- การสร้างกฎอื่นๆ เพื่อแก้ไขความกำกวมในทิศทางใดทิศทางหนึ่ง บางครั้งกฎดังกล่าวอาจเป็นไปตามอำเภอใจและน่าประหลาดใจ ตัวอย่างเช่น ในกฎสำหรับการแก้ไขการโอเวอร์โหลดแบบคงที่ใน C++ ประเภทที่ตรงกันอย่างแม่นยำนั้นถือว่าตรงกันได้ดีกว่าประเภทที่ตรงกันผ่านการอ้างอิงประเภทพื้นฐานหรือพารามิเตอร์ทั่วไป (เทมเพลต) อย่างไรก็ตาม หากการจับคู่ที่เป็นไปได้มีเพียงผ่านประเภทพื้นฐานหรือพารามิเตอร์ทั่วไปเท่านั้น พารามิเตอร์ทั่วไปจะถูกเลือกมากกว่าประเภทพื้นฐาน ซึ่งเป็นกฎที่บางครั้งทำให้เกิดพฤติกรรมที่น่าประหลาดใจ
ประสิทธิภาพ
การใช้งานการเรียกใช้ฟังก์ชันแบบเดี่ยวอย่างมีประสิทธิภาพ รวมถึงในภาษาโปรแกรมที่คอมไพล์แยกต่างหากเป็นโค้ดวัตถุและเชื่อมโยงด้วยลิงเกอร์ระดับต่ำ (ที่ไม่รู้จักภาษา) รวมถึงแบบไดนามิกในเวลาโหลด/เริ่มต้นโปรแกรม หรือแม้กระทั่งภายใต้การควบคุมของโค้ดแอปพลิเคชัน เป็นที่รู้จักกันดี วิธี " vtable " ที่พัฒนาขึ้นใน C++ และภาษาเชิงวัตถุยุคแรกอื่นๆ (ซึ่งแต่ละคลาสมีอาร์เรย์ของตัวชี้ฟังก์ชันที่สอดคล้องกับฟังก์ชันเสมือนของคลาสนั้น) นั้นเร็วเกือบเท่ากับการเรียกใช้เมธอดแบบสแตติก โดยต้องการโอเวอร์เฮด O(1) และการค้นหาหน่วยความจำเพิ่มเติมเพียงครั้งเดียว แม้ในกรณีที่ไม่ได้ปรับให้เหมาะสม อย่างไรก็ตาม วิธี vtable ใช้ชื่อฟังก์ชันและไม่ใช่ประเภทอาร์กิวเมนต์เป็นคีย์ในการค้นหา และไม่สามารถปรับขนาดได้สำหรับกรณีการเรียกใช้ฟังก์ชันแบบหลายรายการ (นอกจากนี้ยังขึ้นอยู่กับกระบวนทัศน์เชิงวัตถุที่มองว่าเมธอดเป็นคุณสมบัติของคลาส ไม่ใช่เอนทิตีอิสระที่ไม่ขึ้นกับประเภทข้อมูลใดๆ)
การนำระบบการจัดส่งหลายช่องทางไปใช้อย่างมีประสิทธิภาพยังคงเป็นปัญหาการวิจัยที่กำลังดำเนินอยู่
นำไปใช้ในทางปฏิบัติ
เพื่อประเมินความถี่ในการใช้ multiple dispatch ในทางปฏิบัติ Muschevici et al. [ 2 ]ได้ศึกษาโปรแกรมที่ใช้ dynamic dispatch พวกเขาได้วิเคราะห์แอปพลิเคชันเก้าแอปพลิเคชัน ซึ่งส่วนใหญ่เป็นคอมไพเลอร์ ที่เขียนด้วยภาษาที่แตกต่างกันหกภาษา ได้แก่Common Lisp Object System , Dylan , Cecil , MultiJava, Diesel และ Nice ผลลัพธ์แสดงให้เห็นว่า 13–32% ของฟังก์ชันทั่วไปใช้ประเภทไดนามิกของอาร์กิวเมนต์หนึ่งตัว ในขณะที่ 2.7–6.5% ใช้ประเภทไดนามิกของอาร์กิวเมนต์หลายตัว ส่วนที่เหลือ 65–93% ของฟังก์ชันทั่วไปมีเมธอดเฉพาะ (overrider) หนึ่งเมธอด ดังนั้นจึงไม่ถือว่าใช้ประเภทไดนามิกของอาร์กิวเมนต์ นอกจากนี้ การศึกษายังรายงานว่า 2–20% ของฟังก์ชันทั่วไปมีการใช้งานฟังก์ชันเฉพาะสองแบบ และ 3–6% มีการใช้งานฟังก์ชันเฉพาะสามแบบ ตัวเลขจะลดลงอย่างรวดเร็วสำหรับฟังก์ชันที่มี overrider เฉพาะมากขึ้น
การเรียกใช้ฟังก์ชันหลายรายการ (Multiple dispatch) ถูกนำมาใช้มากขึ้นในJuliaซึ่งการเรียกใช้ฟังก์ชันหลายรายการเป็นแนวคิดหลักในการออกแบบตั้งแต่เริ่มแรกของภาษา โดยการรวบรวมสถิติแบบเดียวกับที่ Muschevici ทำเกี่ยวกับจำนวนเมธอดเฉลี่ยต่อฟังก์ชันทั่วไป พบว่าไลบรารีมาตรฐานของ Julia ใช้การโอเวอร์โหลดมากกว่าสองเท่าของภาษาอื่นๆ ที่ Muschevici วิเคราะห์ และมากกว่า 10 เท่าในกรณีของ ตัวดำเนิน การไบนารี[ 3 ]
ข้อมูลจากเอกสารเหล่านี้สรุปไว้ในตารางต่อไปนี้ โดยอัตราส่วนการสั่งการDRคือจำนวนเฉลี่ยของวิธีการต่อฟังก์ชันทั่วไป อัตราส่วนการเลือกCRคือค่าเฉลี่ยของกำลังสองของจำนวนวิธีการ (เพื่อวัดความถี่ของฟังก์ชันที่มีจำนวนวิธีการมากได้ดีขึ้น) [ 2 ] [ 3 ]และระดับความเชี่ยวชาญDoSคือจำนวนเฉลี่ยของอาร์กิวเมนต์เฉพาะประเภทต่อวิธีการ (เช่น จำนวนอาร์กิวเมนต์ที่ถูกสั่งการ)
| ภาษา | จำนวนวิธีเฉลี่ย (DR) | อัตราส่วนการเลือก (CR) | ระดับความเชี่ยวชาญ (DoS) |
|---|---|---|---|
| เซซิล[ 2 ] | 2.33 | 63.30 | 1.06 |
| Common Lisp ( CMU ) [ 2 ] | 2.03 | 6.34 | 1.17 |
| Common Lisp ( McCLIM ) [ 2 ] | 2.32 | 15.43 | 1.17 |
| Common Lisp ( Steel Bank ) [ 2 ] | 2.37 | 26.57 | 1.11 |
| ดีเซล[ 2 ] | 2.07 | 31.65 | 0.71 |
| ดิลัน (กวีเดียน) [ 2 ] | 1.74 | 18.27 | 2.14 |
| ดีแลน (OpenDylan) [ 2 ] | 2.51 | 43.84 | 1.23 |
| จูเลีย[ 3 ] | 5.86 | 51.44 | 1.54 |
| จูเลีย (เฉพาะผู้ดำเนินการ) [ 3 ] | 28.13 | 78.06 | 2.01 |
| MultiJava [ 2 ] | 1.50 | 8.92 | 1.02 |
| ดี[ 2 ] | 1.36 | 3.46 | 0.33 |
ทฤษฎี
ทฤษฎีภาษาการส่งคำสั่งหลายรายการได้รับการพัฒนาครั้งแรกโดย Castagna และคณะ โดยการกำหนดแบบจำลองสำหรับฟังก์ชันโอเวอร์โหลดที่มีการผูกมัดแบบล่าช้า[ 4 ] [ 5 ]ซึ่งนำไปสู่การกำหนดรูปแบบอย่างเป็นทางการครั้งแรกของปัญหาความแปรปรวนของประเภท (ความแปรปรวนร่วมและความแปรปรวนผกผัน) ของภาษาเชิงวัตถุ[ 6 ]และวิธีแก้ปัญหาของวิธีการไบนารี[ 7 ]
ตัวอย่าง
การแยกแยะระหว่างการส่งคำสั่งหลายรายการและการส่งคำสั่งรายการเดียวอาจเข้าใจได้ชัดเจนยิ่งขึ้นด้วยตัวอย่าง ลองนึกภาพเกมที่มีวัตถุ (ที่ผู้ใช้มองเห็นได้) เช่น ยานอวกาศและดาวเคราะห์น้อย เมื่อวัตถุสองชิ้นชนกัน โปรแกรมอาจต้องทำสิ่งต่างๆ กันไปตามสิ่งที่ชนกัน
ภาษาที่มีระบบการส่งคำสั่งหลายรายการในตัว
ซี#
C#ได้แนะนำการสนับสนุนมัลติเมธอดแบบไดนามิกในเวอร์ชัน 4 [ 8 ] (เมษายน 2010) โดยใช้คีย์เวิร์ด 'dynamic' ตัวอย่างต่อไปนี้แสดงให้เห็นถึงมัลติเมธอด เช่นเดียวกับภาษาที่มีการกำหนดประเภทแบบคงที่อื่นๆ C# ยังรองรับการโอเวอร์โหลดเมธอดแบบคงที่อีกด้วย[ 9 ] Microsoft คาดหวังว่านักพัฒนาจะเลือกใช้การกำหนดประเภทแบบคงที่มากกว่าการกำหนดประเภทแบบไดนามิกในสถานการณ์ส่วนใหญ่[ 10 ] คีย์เวิร์ด 'dynamic' รองรับการทำงานร่วมกันกับออบเจ็กต์ COM และภาษา .NET ที่มีการกำหนดประเภทแบบไดนามิก
ตัวอย่างด้านล่างนี้ใช้คุณสมบัติที่เพิ่มเข้ามาใน C# 9 และ C# 10
โดยใช้ไลบรารี ColliderLibrary แบบคงที่;Console.WriteLine ( Collide ( new Asteroid ( 101 ) , new Spaceship ( 300 ))); Console.WriteLine ( Collide ( new Asteroid ( 10 ) , new Spaceship ( 10 ))); Console.WriteLine ( Collide ( new Spaceship ( 101 ) , new Spaceship ( 10 ) ) ) ;string Collide ( SpaceObject x , SpaceObject y ) => x . Size > 100 && y . Size > 100 ? "Big boom!" : CollideWith ( x as dynamic , y as dynamic ); // การส่งไปยังเมธอด CollideWith แบบไดนามิกคลาสColliderLibrary { public static string CollideWith ( Asteroid x , Asteroid y ) => "a/a" ; public static string CollideWith ( Asteroid x , Spaceship y ) => "a/s" ; public static string CollideWith ( Spaceship x , Asteroid y ) => "s/a" ; public static string CollideWith ( Spaceship x , Spaceship y ) => "s/s" ; }บันทึกนามธรรมSpaceObject ( int Size ); บันทึกAsteroid ( int Size ) : SpaceObject ( Size ); บันทึกSpaceship ( int Size ) : SpaceObject ( Size );ผลลัพธ์:
เสียงดังสนั่น! a/s s/sกรูวี่
Groovy เป็นภาษา JVMที่เข้ากันได้/ใช้งานร่วมกันได้กับJavaทั่วไปซึ่งแตกต่างจาก Java ตรงที่ใช้การผูกแบบล่าช้า/การส่งต่อแบบหลายรายการ[ 11 ]
/* การใช้งาน Groovy ของตัวอย่าง C# ด้านบน การผูกแบบล่าช้า (Late binding) ทำงานเหมือนกันเมื่อใช้เมธอดที่ไม่ใช่แบบสแตติก หรือคอมไพล์คลาส/เมธอดแบบสแตติก (@CompileStatic annotation) */ class Program { static void main ( String [] args ) { println Collider . collide ( new Asteroid ( 101 ), new Spaceship ( 300 )) println Collider . collide ( new Asteroid ( 10 ), new Spaceship ( 10 )) println Collider . collide ( new Spaceship ( 101 ), new Spaceship ( 10 )) } }คลาสCollider { static String collide ( SpaceObject x , SpaceObject y ) { ( x . size > 100 && y . size > 100 ) ? "big-boom" : collideWith ( x , y ) // เรียกใช้เมธอด collideWith แบบไดนามิก}private static String collideWith ( Asteroid x , Asteroid y ) { "a/a" } private static String collideWith ( Asteroid x , Spaceship y ) { "a/s" } private static String collideWith ( Spaceship x , Asteroid y ) { "s/a" } private static String collideWith ( Spaceship x , Spaceship y ) { "s/s" } }คลาสSpaceObject { int size SpaceObject ( int size ) { this . size = size } }@InheritConstructors class Asteroid extends SpaceObject {} @InheritConstructors class Spaceship extends SpaceObject {}ลิสปาร์กทั่วไป
ในภาษาที่มีการเรียกใช้ฟังก์ชันหลายทาง เช่นCommon Lispรูปแบบอาจเป็นแบบนี้ (แสดงตัวอย่าง Common Lisp):
( defclass asteroid () (( size :reader size :initarg :size ))) ( defclass spaceship () (( size :reader size :initarg :size ))) ( defun space-object ( class size ) ( make-instance class :size size ))collide-with เป็นฟังก์ชันทั่วไปที่มีการเรียกใช้หลายแบบ( defmethod collide-with (( x asteroid ) ( y asteroid )) "a/a" ) ( defmethod collide-with (( x asteroid ) ( y spaceship )) "a/s" ) ( defmethod collide-with (( x spaceship ) ( y asteroid )) "s/a" ) ( defmethod collide-with (( x spaceship ) ( y spaceship )) "s/s" )( defun collide ( x y ) ( if ( and ( > ( size x ) 100 ) ( > ( size y ) 100 )) "big-boom" ( collide-with x y )))( พิมพ์( ชนกัน( วัตถุอวกาศ'ดาวเคราะห์น้อย 101 ') ( วัตถุอวกาศ'ยานอวกาศ300 '))) ( พิมพ์( ชนกัน( วัตถุอวกาศ'ดาวเคราะห์ น้อย 10 ') ( วัตถุอวกาศ'ยานอวกาศ10 '))) ( พิมพ์( ชนกัน( วัตถุอวกาศ'ยานอวกาศ101 ') ( วัตถุอวกาศ'ยานอวกาศ10 ')))และเช่นเดียวกันสำหรับวิธีการอื่นๆ ไม่มีการใช้การทดสอบแบบชัดเจนและการแปลงประเภทข้อมูลแบบไดนามิก
ในกรณีที่มีการเรียกใช้เมธอดหลายแบบ แนวคิดดั้งเดิมที่ว่าเมธอดถูกกำหนดไว้ในคลาสและบรรจุอยู่ในอ็อบเจ็กต์นั้นดูไม่น่าสนใจอีกต่อไป เพราะ เมธอด collide-withข้างต้นแต่ละตัวจะเชื่อมโยงกับสองคลาสที่แตกต่างกัน ไม่ใช่คลาสเดียว ดังนั้นไวยากรณ์พิเศษสำหรับการเรียกใช้เมธอดจึงหายไปโดยทั่วไป ทำให้การเรียกใช้เมธอดดูเหมือนการเรียกใช้ฟังก์ชันทั่วไป และเมธอดจะถูกจัดกลุ่มไม่ใช่ในคลาส แต่ในฟังก์ชันทั่วไป
จูเลีย
Juliaมีการเรียกใช้ฟังก์ชันแบบหลายทางในตัว และถือเป็นหัวใจสำคัญของการออกแบบภาษา[ 3 ] เวอร์ชัน Julia ของตัวอย่างข้างต้นอาจมีลักษณะดังนี้:
ประเภทนามธรรมSpaceObject สิ้นสุดstruct Asteroid <: SpaceObject size :: Int end struct Spaceship <: SpaceObject size :: Int endcollide_with ( :: Asteroid , :: Spaceship ) = "a/s" collide_with ( :: Spaceship , :: Asteroid ) = "s/a" collide_with ( :: Spaceship , :: Spaceship ) = "s/s" collide_with ( :: Asteroid , :: Asteroid ) = "a/a"collide ( x :: SpaceObject , y :: SpaceObject ) = ( x . size > 100 && y . size > 100 ) ? "Big boom!" : collide_with ( x , y )ผลลัพธ์:
จูเลีย> ชน( ดาวเคราะห์ น้อย ( 101 ), ยานอวกาศ( 300 )) "ตูมใหญ่!"จูเลีย> ชน( ดาวเคราะห์น้อย( 10 ), ยานอวกาศ( 10 )) "a/s"จูเลีย> ชน( ยานอวกาศ( 101 ), ยานอวกาศ( 10 )) "s/s"ราคุ
เช่นเดียวกับ Perl, Rakuใช้แนวคิดที่ได้รับการพิสูจน์แล้วจากภาษาโปรแกรมอื่นๆ และระบบประเภทข้อมูลได้แสดงให้เห็นถึงข้อได้เปรียบที่น่าสนใจในการวิเคราะห์โค้ดฝั่งคอมไพเลอร์และความหมายที่ทรงพลังฝั่งผู้ใช้ผ่านการเรียกใช้ฟังก์ชันหลายทาง (multiple dispatch)
ประกอบด้วยทั้งมัลติเมธอดและมัลติซับรูทีน เนื่องจากตัวดำเนินการส่วนใหญ่เป็นซับรูทีน จึงมีตัวดำเนินการที่ถูกส่งหลายตัวด้วยเช่นกัน
นอกเหนือจากข้อจำกัดด้านประเภทตามปกติแล้ว ยังมี ข้อจำกัด ด้านตำแหน่งที่ช่วยให้สามารถสร้างรูทีนย่อยที่เฉพาะเจาะจงมากยิ่งขึ้นได้
เซตย่อยMass ของReal ที่0 ^..^ Inf ; บทบาทStellar-Object { มีMass $.mass เป็นสิ่งจำเป็น ; ชื่อเมธอด () ส่งคืนStr {...}; } คลาสAsteroid ทำหน้าที่เป็นStellar-Object { เมธอดname () { 'an asteroid' } } คลาสSpaceship ทำStellar-Object { มีStr $.name = 'ยานอวกาศที่ไม่มีชื่อ' ; } my Str @destroyed = < obliterated destroyed mangled >; my Str @damaged = « damaged 'collided with' 'was damaged by' »; # เราเพิ่มตัวเลือกหลายตัวให้กับตัวดำเนินการเปรียบเทียบตัวเลขเพราะเรากำลังเปรียบเทียบตัวเลข# แต่การแปลงวัตถุเป็นประเภท Numeric นั้นไม่สมเหตุสมผล# (ถ้ามีการแปลง เราก็ไม่จำเป็นต้องเพิ่มตัวดำเนินการเหล่านี้) # เรายังสามารถกำหนดตัวดำเนินการใหม่ทั้งหมดด้วยวิธีเดียวกันนี้ได้อีกด้วยmulti sub infix: « <=> » ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . mass <=> $b . mass } multi sub infix: « < » ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . mass < $b . mass } multi sub infix: « > » ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . mass > $b . mass } multi sub infix: « == » ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . มวล == $b . มวล } # กำหนดมัลติดิสแพตช์เลอร์ใหม่ และเพิ่มข้อจำกัดประเภทให้กับพารามิเตอร์# หากเราไม่ได้กำหนดไว้ เราจะได้ดิสแพตช์เลอร์ทั่วไปที่ไม่มีข้อจำกัดproto sub collide ( Stellar-Object:D $, Stellar-Object:D $ ) {*} # ไม่จำเป็นต้องระบุประเภทซ้ำที่นี่ เนื่องจากเหมือนกับต้นแบบแล้ว# ข้อจำกัด 'where' ในทางเทคนิคแล้วใช้กับ $b เท่านั้น ไม่ใช่ลายเซ็นทั้งหมด# โปรดทราบว่าข้อจำกัด 'where' ใช้ตัวเลือกตัวดำเนินการ `<` ที่เราเพิ่มไว้ก่อนหน้านี้multi sub collide ( $a , $b where $a < $b ) { say "$a.name() ถูก @destroyed.pick() โดย $b.name()" ; } multi sub collide ( $a , $b where $a > $b ) { # ส่งต่อไปยังผู้สมัครก่อนหน้าโดยสลับอาร์กิวเมนต์samewith $b , $a ; } # อันนี้ต้องอยู่หลังสองอันแรกเพราะอันอื่นๆ# มีข้อจำกัด 'where' ซึ่งจะถูกตรวจสอบตามลำดับที่เขียนซับรูทีน (อันนี้จะตรงกันเสมอ) multi sub collide ( $a , $b ) { # สุ่มลำดับmy ( $n1 , $n2 ) = ( $a . name , $b . name ). pick (*); say "$n1 @damaged.pick() $n2" ; } # ผู้สมัครสองคนต่อไปนี้สามารถอยู่ตรงไหนก็ได้หลังต้นแบบ# เนื่องจากพวกเขามีประเภทที่เฉพาะเจาะจงมากกว่าสามคนก่อนหน้า# หากยานอวกาศมีมวลไม่เท่ากัน ผู้สมัครสองรายแรกจะถูกเรียกแทนmulti sub collide ( Spaceship $a , Spaceship $b where $a == $b ){ my ( $n1 , $n2 ) = ( $a . name , $b . name ). pick (*); say "$n1 ชนกับ $n2 และยานทั้งสองลำเป็น " , ( @destroyed . pick , 'left damaged' ). pick ; } # คุณสามารถแยกแอตทริบิวต์ออกเป็นตัวแปรภายในลายเซ็นได้# คุณยังสามารถกำหนดข้อจำกัดให้กับพวกมันได้ด้วย `(:mass($a) where 10)` multi sub collide ( Asteroid $ (: mass ( $a )), Asteroid $ (: mass ( $b )) ){ say "ดาวเคราะห์น้อยสองดวงชนกันและรวมกันเป็นดาวเคราะห์น้อยขนาดใหญ่หนึ่งดวงที่มีมวล { $a + $b }" ; } ยานอวกาศของฉัน$Enterprise .= ใหม่ (: มวล ( 1 ),: ชื่อ ( 'The Enterprise' )); ชนกับดาวเคราะห์ น้อย . ใหม่ (: มวล ( .1 )), $Enterprise ; ชนกับ$Enterprise , ยานอวกาศ . ใหม่ (: มวล ( .1 )); ชนกับ$Enterprise , ดาวเคราะห์น้อย . ใหม่ (: มวล ( 1 )); ชนกับ$Enterprise , ยานอวกาศ . ใหม่ (: มวล ( 1 )); ชนกับดาวเคราะห์ น้อย . ใหม่ (: มวล ( 10 )), ดาวเคราะห์น้อย . ใหม่ (: มวล ( 5 )); การขยายภาษาด้วยไลบรารีการส่งคำสั่งหลายรายการ
โค้ด JavaScript
ในภาษาที่ไม่รองรับการเรียกใช้ฟังก์ชันหลายรายการในระดับคำจำกัดความของภาษาหรือระดับไวยากรณ์ มักจะสามารถเพิ่มการเรียกใช้ฟังก์ชันหลายรายการได้โดยใช้ ส่วนขยาย ไลบรารี JavaScript และ TypeScript ไม่รองรับมัลติเมธอดในระดับไวยากรณ์ แต่สามารถเพิ่มการเรียกใช้ฟังก์ชันหลายรายการผ่านไลบรารีได้ ตัวอย่างเช่นแพ็กเกจ multimethod [ 12 ]มีการใช้งานการเรียกใช้ฟังก์ชันหลายรายการแบบทั่วไป
เวอร์ชันที่มีการกำหนดประเภทข้อมูลแบบไดนามิกใน>
import { multi , method } from '@arrows/multimethod'คลาสAsteroid {} คลาสSpaceship {}const collideWith = multi ( method ([ Asteroid , Asteroid ], ( x , y ) => { // จัดการกับกรณีที่ดาวเคราะห์น้อยชนกับดาวเคราะห์น้อย}), method ([ Asteroid , Spaceship ], ( x , y ) => { // จัดการกับกรณีที่ดาวเคราะห์น้อยชนกับยานอวกาศ}), method ([ Spaceship , Asteroid ], ( x , y ) => { // จัดการกับกรณีที่ยานอวกาศชนกับดาวเคราะห์น้อย}), method ([ Spaceship , Spaceship ], ( x , y ) => { // จัดการกับกรณีที่ยานอวกาศชนกับยานอวกาศ}), )เวอร์ชันที่มีการกำหนดประเภทข้อมูลแบบคงที่ใน TypeScript:
import { multi , method , Multi } from '@arrows/multimethod'คลาสAsteroid {} คลาสSpaceship {}type CollideWith = Multi & { ( x : Asteroid , y : Asteroid ) : void ( x : Asteroid , y : Spaceship ) : void ( x : Spaceship , y : Asteroid ) : void ( x : Spaceship , y : Spaceship ) : void }const collideWith : CollideWith = multi ( method ([ Asteroid , Asteroid ], ( x , y ) => { // จัดการกับกรณีที่ดาวเคราะห์น้อยชนกับดาวเคราะห์น้อย}), method ([ Asteroid , Spaceship ], ( x , y ) => { // จัดการกับกรณีที่ดาวเคราะห์น้อยชนกับยานอวกาศ}), method ([ Spaceship , Asteroid ], ( x , y ) => { // จัดการกับกรณีที่ยานอวกาศชนกับดาวเคราะห์น้อย}), method ([ Spaceship , Spaceship ], ( x , y ) => { // จัดการกับกรณีที่ยานอวกาศชนกับยานอวกาศ}), )ไพธอน
สามารถเพิ่มการส่งคำสั่งหลายรายการลงในPython ได้ โดยใช้ ส่วนขยาย ไลบรารีตัวอย่างเช่น การใช้โมดูลmultimethod.py [ 13 ]และโมดูลmultimethods.py [ 14 ]ซึ่งให้มัลติเมธอดแบบ CLOS สำหรับPythonโดยไม่ต้องเปลี่ยนไวยากรณ์พื้นฐานหรือคำหลักของภาษา
จากการพิมพ์นำเข้าใดๆimport game_behaviors from game_objects import Asteroid , Spaceship from multimethods import Dispatchcollide : Dispatch = Dispatch () collide . add_rule (( Asteroid , Spaceship ), game_behaviors . as_func ) collide . add_rule (( Spaceship , Spaceship ), game_behaviors . ss_func ) collide . add_rule (( Spaceship , Asteroid ), game_behaviors . sa_func )def aa_func ( a : Any , b : Any ) -> None : """พฤติกรรมเมื่อดาวเคราะห์น้อยชนดาวเคราะห์น้อย""" # ...กำหนดพฤติกรรมใหม่...collide.add_rule ( ( Asteroid , Asteroid ) , aa_func )# ...ต่อมา... ชนกัน( สิ่งที่ 1 , สิ่งที่ 2 )ในแง่การทำงาน มันคล้ายกับตัวอย่างของ CLOS มาก แต่ไวยากรณ์เป็นแบบ Python ทั่วไป
โดยใช้decorators (ซึ่งแนะนำตั้งแต่ Python 2.4) Guido van Rossumได้สร้างตัวอย่างการใช้งาน multimethods [ 15 ]ด้วยไวยากรณ์ที่เรียบง่าย:
@multimethod ( Asteroid , Asteroid ) def collide ( a : Asteroid , b : Asteroid ) -> None : """พฤติกรรมเมื่อดาวเคราะห์น้อยชนกับดาวเคราะห์น้อย""" # ...กำหนดพฤติกรรมใหม่...@multimethod ( Asteroid , Spaceship ) def collide ( a : Asteroid , b : Spaceship ) -> None : """พฤติกรรมเมื่อดาวเคราะห์น้อยชนยานอวกาศ""" # ...กำหนดพฤติกรรมใหม่...# ... กำหนดกฎมัลติเมธอดอื่นๆ ...จากนั้นจึงอธิบายเพิ่มเติมเกี่ยวกับตัวตกแต่งแบบหลายเมธอด (multimethod decorator)
แพ็คเกจ PEAK-Rules ให้การส่งคำสั่งหลายรายการด้วยไวยากรณ์ที่คล้ายกับตัวอย่างข้างต้น[ 16 ]ต่อมาถูกแทนที่ด้วย PyProtocols [ 17 ]
ไลบรารี Reg ยังรองรับการส่งคำสั่งหลายรายการและคำสั่งเงื่อนไขด้วย[ 18 ]
ด้วยการนำtype hints มาใช้ ทำให้สามารถเรียกใช้ multiple dispatch ได้ด้วยไวยากรณ์ที่ง่ายกว่าเดิม ตัวอย่างเช่น การใช้plum- dispatch
จากการจัดส่งนำเข้าพลัม@dispatch def collide ( a : Asteroid , b : Asteroid ) -> None : """พฤติกรรมเมื่อดาวเคราะห์น้อยชนกับดาวเคราะห์น้อย""" # ...กำหนดพฤติกรรมใหม่...@dispatch def collide ( a : Asteroid , b : Spaceship ) -> None : """พฤติกรรมเมื่อดาวเคราะห์น้อยชนยานอวกาศ""" # ...กำหนดพฤติกรรมใหม่...# ...กำหนดกฎเพิ่มเติม...จำลองการส่งหลายทาง
ซี
ภาษา C ไม่มีฟังก์ชันการเรียกใช้ฟังก์ชันแบบไดนามิก ดังนั้นจึงต้องเขียนโค้ดเองในรูปแบบใดรูปแบบหนึ่ง บ่อยครั้งที่ใช้ enum เพื่อระบุชนิดย่อยของอ็อบเจ็กต์ การเรียกใช้ฟังก์ชันแบบไดนามิกสามารถทำได้โดยการค้นหาค่านี้ในตารางสาขาของตัวชี้ฟังก์ชัน นี่คือตัวอย่างง่ายๆ ในภาษา C:
typedef void ( * CollisionCase )( void );void collisionAsteroidAsteroid ( void ) { // จัดการการชนกันระหว่างดาวเคราะห์น้อย... }void collisionAsteroidSpaceship ( void ) { // จัดการการชนกันระหว่างดาวเคราะห์น้อยและยานอวกาศ... }void collisionSpaceshipAsteroid ( void ) { // จัดการการชนกันระหว่างยานอวกาศกับดาวเคราะห์น้อย... }void collisionSpaceshipSpaceship ( void ) { // จัดการการชนกันระหว่างยานอวกาศ... }typedef enum { COLLIDEABLE_ASTEROID = 0 , COLLIDEABLE_SPACESHIP , COLLIDEABLE_COUNT // ไม่ใช่ชนิดของ Collideable เอง แต่ใช้เพื่อค้นหาจำนวนวัตถุอวกาศที่กำหนดไว้} Collideable ;CollisionCase collisionCases [ COLLIDEABLE_COUNT ][ COLLIDEABLE_COUNT ] = { { & collisionAsteroidAsteroid , & collisionAsteroidSpaceship }, { & collisionSpaceshipAsteroid , & collisionSpaceshipSpaceship } };void collide ( Collideable a , Collideable b ) { ( * collisionCases [ a ][ b ])(); }int main ( void ) { collide ( COLLIDEABLE_SPACESHIP , COLLIDEABLE_ASTEROID ); }ด้วยไลบรารี C Object System [ 19 ] C รองรับการส่งแบบไดนามิกคล้ายกับ CLOS ซึ่งสามารถขยายได้อย่างเต็มที่และไม่จำเป็นต้องจัดการวิธีการด้วยตนเอง ข้อความแบบไดนามิก (วิธีการ) จะถูกส่งโดยตัวส่งของ COS ซึ่งเร็วกว่าObjective-Cนี่คือตัวอย่างใน COS:
#include <stdio.h> #include <cos/Object.h> #include <cos/gen/object.h>// คลาสdefclass ( Asteroid ) // สมาชิกข้อมูลendclassdefclass ( Spaceship ) // สมาชิกข้อมูลendclass// เจเนริกdefgeneric ( bool , collide_with , _1 , _2 );// มัลติเมธอดdefmethod ( bool , collide_with , Asteroid , Asteroid ) // จัดการกับกรณีที่ดาวเคราะห์น้อยชนกับดาวเคราะห์น้อยendmethoddefmethod ( bool , collide_with , Asteroid , Spaceship ) // จัดการกับกรณีที่ดาวเคราะห์น้อยชนยานอวกาศendmethoddefmethod ( bool , collide_with , Spaceship , Asteroid ) // จัดการกับกรณีที่ยานอวกาศชนกับดาวเคราะห์น้อยendmethoddefmethod ( bool , collide_with , Spaceship , Spaceship ) // จัดการกับกรณีที่ยานอวกาศชนกันendmethod// ตัวอย่างการใช้งานint main ( void ) { OBJ a = gnew ( Asteroid ); OBJ s = gnew ( Spaceship );printf ( "<a,a> = %d \n " , collide_with ( a , a )); printf ( "<a,s> = %d \n " , collide_with ( a , s )); printf ( "<s,a> = %d \n " , collide_with ( s , a )); printf ( "<s,s> = %d \n " , collide_with ( s , s ));grelease ( a ); grelease ( s ); }ซี++
ข้อมูล ณ ปี 2021C ++รองรับการเรียกใช้แบบเดี่ยวเท่านั้น แม้ว่าการเพิ่มเมธอดหลายตัว (การเรียกใช้แบบหลายรายการ) จะได้รับการเสนอโดยBjarne Stroustrup (และผู้ร่วมงาน) ในปี 2550 [ 20 ]วิธีการแก้ปัญหาข้อจำกัดนี้คล้ายคลึงกัน คือ ใช้รูปแบบผู้เยี่ยมชมการแปลงแบบไดนามิก หรือไลบรารี:
// ตัวอย่างการใช้การเปรียบเทียบประเภทขณะรันไทม์ผ่าน dynamic_castคลาสCollideable { public : virtual void collideWith ( Collideable & other ) = 0 ; };class Asteroid : public Collideable { public : void collideWith ( Collideable & other ) { // การแปลง dynamic_cast เป็นชนิดพอยเตอร์จะคืนค่า nullptr หากการแปลงล้มเหลว// (การแปลง dynamic_cast เป็นชนิดอ้างอิงจะโยนข้อยกเว้นหากล้มเหลว) if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // จัดการการชนกันระหว่างดาวเคราะห์น้อยกับดาวเคราะห์น้อย} else if ( Spaceship * spaceship = dynamic_cast < Spaceship *> ( & other )) { // จัดการการชนกันระหว่างดาวเคราะห์น้อยกับยานอวกาศ} else { // การจัดการการชนกันตามค่าเริ่มต้น} } };คลาสSpaceship : public Collideable { public : void collideWith ( Collideable & other ) { if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // จัดการการชนกันระหว่างยานอวกาศกับดาวเคราะห์น้อย} else if ( Spaceship * spaceship = dynamic_cast < Spaceship *> ( & other )) { // จัดการการชนกันระหว่างยานอวกาศกับยานอวกาศ} else { // จัดการการชนกันตามค่าเริ่มต้น} } };หรือตารางค้นหา ตัวชี้ไปยังเมธอด :
import std ;การใช้งานstd :: unordered_map ;คลาสCollideable { protected : explicit Collideable ( uint32_t cid ) : tid { cid } {}virtual ~ Collideable () = default ;const uint32_t tid ; // ประเภท idusing CollisionHandler = void ( Collideable ::* )( Collideable & other ); using CollisionHandlers = unordered_map < uint64_t , CollisionHandler > ;static void addHandler ( uint32_t id1 , uint32_t id2 , CollisionHandler handler ) { collisionCases.insert ( CollisionHandlers :: value_type ( key ( id1 , id2 ) , handler )) ; }static uint64_t key ( uint32_t id1 , uint32_t id2 ) { return uint64_t ( id1 ) << 32 | id2 ; }static inline CollisionHandlers collisionCases {}; public : void collideWith ( Collideable & other ) { if ( auto handler = collisionCases . find ( key ( tid , other . tid )); handler != collisionCases . end ()) { ( this ->* handler -> second )( other ); // เรียกใช้เมธอดโดยใช้ตัวชี้} else { // การจัดการการชนกันตามค่าเริ่มต้น} } };คลาสAsteroid : public Collideable { private : void asteroidCollision ( Collideable & other ) { // จัดการการชนกันระหว่างดาวเคราะห์น้อย} }void spaceshipCollision ( Collideable & other ) { // จัดการการชนกันระหว่างดาวเคราะห์น้อยกับยานอวกาศ} public : Asteroid () : Collideable ( cid ) {}~ ดาวเคราะห์น้อย() = ค่าเริ่มต้น;static void initCases (); static inline const uint32_t cid = typeid ( Asteroid ) .hash_code (); };คลาสSpaceship : public Collideable { private : void asteroidCollision ( Collideable & other ) { // จัดการการชนกันระหว่างยานอวกาศกับดาวเคราะห์น้อย} }void spaceshipCollision ( Collideable & other ) { // จัดการการชนกันระหว่างยานอวกาศ} public : Spaceship () : Collideable ( cid ) {}~ ยานอวกาศ() = ค่าเริ่มต้น;static void initCases (); static inline const uint32_t cid = typeid ( Spaceship ) .hash_code (); // รหัสคลาส};void Asteroid::initCases () { addHandler ( cid , cid , CollisionHandler ( & Asteroid :: asteroidCollision )); addHandler ( cid , Spaceship :: cid , CollisionHandler ( & Asteroid :: spaceshipCollision )); }void Spaceship::initCases () { addHandler ( cid , Asteroid :: cid , CollisionHandler ( & Spaceship :: asteroidCollision )); addHandler ( cid , cid , CollisionHandler ( & Spaceship :: spaceshipCollision )); }int main ( int argc , char * argv []) { Asteroid :: initCases (); Spaceship :: initCases ();ดาวเคราะห์น้อย a1 ; ดาวเคราะห์น้อยa2 ; ยาน อวกาศ s1 ; ยานอวกาศs2 ;a1.collideWith ( a2 ) ; a1.collideWith ( s1 ) ;s1.collideWith ( s2 ) ; s1.collideWith ( a1 ) ; }ไลบรารีYOMM2 [ 21 ] ให้การใช้งานมัลติเมธอดแบบเปิด ที่รวดเร็วและตั้งฉากกัน
ไวยากรณ์สำหรับการประกาศเมธอดแบบเปิดได้รับแรงบันดาลใจจากข้อเสนอสำหรับการใช้งานในภาษา C++ ดั้งเดิม ไลบรารีนี้กำหนดให้ผู้ใช้ต้องลงทะเบียนคลาสทั้งหมดที่ใช้เป็นอาร์กิวเมนต์เสมือน (และคลาสย่อยของคลาสเหล่านั้น) แต่ไม่จำเป็นต้องแก้ไขโค้ดที่มีอยู่ เมธอดถูกนำไปใช้เป็นฟังก์ชัน C++ แบบอินไลน์ทั่วไป สามารถโอเวอร์โหลดได้ และสามารถส่งผ่านโดยใช้พอยเตอร์ได้ ไม่มีข้อจำกัดเกี่ยวกับจำนวนอาร์กิวเมนต์เสมือน และสามารถผสมผสานกับอาร์กิวเมนต์ที่ไม่ใช่เสมือนได้อย่างอิสระ
ไลบรารีนี้ใช้เทคนิคหลายอย่างร่วมกัน (ตารางการส่งคำสั่งแบบบีบอัด ตารางแฮชจำนวนเต็มที่ไม่มีการชนกัน) เพื่อเรียกใช้เมธอดในเวลาคงที่ พร้อมทั้งลดการใช้หน่วยความจำ การส่งคำสั่งเรียกไปยังเมธอดแบบเปิดที่มีอาร์กิวเมนต์เสมือนเพียงตัวเดียวใช้เวลามากกว่าการเรียกฟังก์ชันสมาชิกเสมือนทั่วไปเพียง 15-30% เท่านั้น เมื่อใช้คอมไพเลอร์ที่ปรับแต่งประสิทธิภาพ สมัยใหม่
ตัวอย่างเกม Asteroids สามารถนำไปประยุกต์ใช้ได้ดังนี้:
#include <yorel/yomm2/keywords.hpp>import std ;โดยใช้std :: unique_ptr ;คลาสCollideable { public : virtual ~ Collideable () = default ; };คลาสAsteroid : public Collideable { // ... };คลาสยานอวกาศ: สาธารณะที่สามารถชนได้{ // ... };register_classes ( Collideable , Spaceship , Asteroid );declare_method ( void , collideWith , ( virtual_ < Collideable &> , virtual_ < Collideable &> ));define_method ( void , collideWith , ( Collideable & left , Collideable & right )) { // การจัดการการชนแบบเริ่มต้น}define_method ( void , collideWith , ( Asteroid & left , Asteroid & right )) { // จัดการการชนกันระหว่างดาวเคราะห์น้อย}define_method ( void , collideWith , ( Asteroid & left , Spaceship & right )) { // จัดการการชนกันระหว่างดาวเคราะห์น้อยและยานอวกาศ}define_method ( void , collideWith , ( Spaceship & left , Asteroid & right )) { // จัดการการชนกันระหว่างยานอวกาศและดาวเคราะห์น้อย}define_method ( void , collideWith , ( Spaceship & left , Spaceship & right )) { // จัดการการชนกันระหว่างยานอวกาศ}int main ( int argc , char * argv []) { yorel :: yomm2 :: update_methods ();unique_ptr < Collideable > a1 ( std :: make_unique < Asteroid > ()); unique_ptr < Collideable > a2 ( std :: make_unique < Asteroid > ()); unique_ptr < Collideable > s1 ( std :: make_unique < Spaceship > ()); unique_ptr < Collideable > s2 ( std :: make_unique < Spaceship > ()); // หมายเหตุ: ประเภทถูกลบออกบางส่วนcollideWith ( * a1 , * a2 ); // การชนกันระหว่างดาวเคราะห์น้อยกับดาวเคราะห์น้อยcollideWith ( * a1 , * s1 ); // การชนกันระหว่างดาวเคราะห์น้อยกับยานอวกาศcollideWith ( * s1 , * a1 ); // การชนกันระหว่างยานอวกาศกับดาวเคราะห์น้อยcollideWith ( * s1 , * s2 ); // การชนกันระหว่างยานอวกาศกับยานอวกาศส่งคืนค่า0 ; }Stroustrup กล่าวในThe Design and Evolution of C++ว่าเขาชอบแนวคิดของมัลติเมธอดและพิจารณาที่จะนำไปใช้ใน C++ แต่กล่าวว่าไม่สามารถหาตัวอย่างการใช้งานที่มีประสิทธิภาพ (เทียบเท่ากับฟังก์ชันเสมือน) และแก้ไขปัญหาความกำกวมของประเภทที่อาจเกิดขึ้นได้ จากนั้นเขากล่าวว่าถึงแม้ฟีเจอร์นี้จะยังคงเป็นสิ่งที่ดีที่จะมี แต่ก็สามารถนำไปใช้ได้โดยประมาณโดยใช้การเรียกใช้แบบคู่หรือตารางค้นหาตามประเภทดังที่ได้อธิบายไว้ในตัวอย่าง C/C++ ข้างต้น ดังนั้นจึงเป็นฟีเจอร์ที่มีลำดับความสำคัญต่ำสำหรับการแก้ไขภาษาในอนาคต[ 22 ]
ดี
ข้อมูล ณ ปี 2021เช่นเดียวกับภาษาการเขียนโปรแกรมเชิงวัตถุอื่นๆ อีกมากมายDรองรับเฉพาะการส่งคำสั่งแบบเดี่ยวเท่านั้น อย่างไรก็ตาม เป็นไปได้ที่จะจำลองมัลติเมธอดแบบเปิดเป็นฟังก์ชันไลบรารีใน D ไลบรารีopenmethods [ 23 ]เป็นตัวอย่างหนึ่ง
// การประกาศเมทริกซ์บวก( เมทริกซ์เสมือน! , เมท ริกซ์เสมือน! );// การเขียนทับสำหรับอ็อบเจ็กต์ DenseMatrix สองตัว@method Matrix _plus ( DenseMatrix a , DenseMatrix b ) { const int nr = a . rows ; const int nc = a . cols ; assert ( a . nr == b . nr ); assert ( a . nc == b . nc ); auto result = new DenseMatrix ; result . nr = nr ; result . nc = nc ; result . elems . length = a . elems . length ; result . elems [] = a . elems [] + b . elems []; return result ; }// การเขียนทับสำหรับอ็อบเจ็กต์ DiagonalMatrix สองตัว@method Matrix _plus ( DiagonalMatrix a , DiagonalMatrix b ) { assert ( a . rows == b . rows ); double [] sum ; sum . length = a . elems . length ; sum [] = a . elems [] + b . elems []; return new DiagonalMatrix ( sum ); }ชวา
ในภาษาที่มีการเรียกใช้ฟังก์ชันแบบ single dispatch เท่านั้น เช่นJavaการเรียกใช้ฟังก์ชันแบบ multiple dispatch สามารถจำลองได้ด้วยการเรียกใช้ฟังก์ชันแบบ single dispatch หลายระดับ:
อินเทอร์เฟซCollideable { void collideWith ( Collideable other );// เมธอดเหล่านี้จะต้องมีชื่อที่แตกต่างกันในภาษาที่ไม่มีการโอเวอร์โหลดเมธอดvoid collideWith ( Asteroid asteroid ); void collideWith ( Spaceship spaceship ); }คลาสAsteroid implements Collideable { public void collideWith ( Collideable other ) { // เรียก collideWith บนวัตถุอื่นother . collideWith ( this ); } }public void collideWith ( Asteroid asteroid ) { // จัดการการชนกันระหว่างดาวเคราะห์น้อย}public void collideWith ( Spaceship spaceship ) { // จัดการการชนกันระหว่างดาวเคราะห์น้อยกับยานอวกาศ} }คลาสSpaceship implements Collideable { public void collideWith ( Collideable other ) { // เรียก collideWith บนวัตถุอื่นother . collideWith ( this ); } }public void collideWith ( Asteroid asteroid ) { // จัดการการชนกันระหว่างยานอวกาศกับดาวเคราะห์น้อย}public void collideWith ( Spaceship spaceship ) { // จัดการการชนกันระหว่างยานอวกาศ} }instanceofสามารถใช้การตรวจสอบขณะทำงานได้ ทั้งในระดับใดระดับหนึ่งหรือทั้งสองระดับ
การสนับสนุนในภาษาโปรแกรม
กระบวนทัศน์หลัก
รองรับวิธีการหลายวิธีทั่วไป
- C# 4.0 [ 25 ]
- เซซิล[ 26 ]
- โคลจูร์[ 27 ]
- Common Lisp (ผ่านCommon Lisp Object System ) [ 28 ]
- ดีแลน[ 29 ]
- Emacs Lisp (ผ่านcl-defmethod )
- ป้อมปราการ[ 30 ]
- กรูวี่[ 31 ]
- บ่วง[ 32 ] [ 33 ]
- Nimจนถึง v0.19.x (ตั้งแต่ v0.20.0 จำเป็นต้องส่งแฟล็กคอมไพเลอร์) [ 34 ]
- ราคุ[ 35 ]
- R [ 36 ]
- TADS [ 37 ]
- Visual Basic (.NET) (VB.NET) [ 38 ]ผ่านการผูกแบบล่าช้า รวมถึงผ่าน.Net DLR [ 39 ]
- Wolfram Language [ 40 ]ผ่านการจับคู่รูปแบบ เชิงสัญลักษณ์
- Xtend [ 41 ]
ผ่านส่วนขยาย
- ภาษา ใดก็ได้ ในเฟรมเวิร์ก . NET (ผ่านไลบรารีMultiMethods.NET )
- ภาษาซี (ผ่านไลบรารีC Object System )
- C# (ผ่านไลบรารีmultimethod-sharp )
- C++ (ผ่านไลบรารีyomm2 , multimethodsและomm )
- D (ผ่านทางไลบรารีopenmethods )
- ปัจจัย (ผ่านคำศัพท์มาตรฐานของวิธีการหลายวิธี )
- ภาษาจาวา (โดยใช้ส่วนขยายMultiJava )
- JavaScript (ผ่านแพ็กเกจ@arrows/multimethod )
- ภาษา Perl (ผ่านโมดูลClass::Multimethods )
- Python (ผ่านPEAK-Rules , RuleDispatch , gnosis.magic.multimethods , PyMultimethods , multipledispatchหรือplum-dispatch )
- แร็กเก็ต (ผ่านmultimethod-lib )
- รูบี้ (ผ่านไลบรารีThe Multiple Dispatch LibraryและMultimethod PackageและVlx-Multimethods Package )
- รูปแบบ (เช่น ผ่านTinyCLOS )
- TypeScript (ผ่านแพ็กเกจ@arrows/multimethod )
ดูเพิ่มเติม
ลิงก์ภายนอก
- Stroustrup, Bjarne; Solodkyy, Yuriy; Pirkelbauer, Peter (2007). Open Multi-Methods for C++ (PDF) . การประชุมวิชาการนานาชาติ ACM ครั้งที่ 6 ว่าด้วยการเขียนโปรแกรมเชิงกำเนิดและวิศวกรรมส่วนประกอบ
- "การส่งคำสั่งแบบหลายทางแบบไดนามิก" . docs.racket-lang.org . สืบค้นเมื่อ2018-03-12 .