ประเภทครอบครัว
ในวิทยาการคอมพิวเตอร์ตระกูลประเภทจะเชื่อมโยงประเภทข้อมูลกับประเภทข้อมูล อื่น ๆ โดยใช้ ฟังก์ชันระดับประเภทที่กำหนดโดยชุดอินสแตนซ์ที่ถูกต้องของประเภทอินพุตและประเภทเอาต์พุตที่สอดคล้องกันซึ่งไม่มีขีดจำกัด[ 1 ]
ตระกูลประเภท (Type families) เป็นคุณลักษณะของระบบประเภท บางระบบ ที่อนุญาตให้กำหนดฟังก์ชันบางส่วนระหว่างประเภทต่างๆ โดยใช้การจับคู่รูปแบบ (pattern matching ) ซึ่งแตกต่างจากตัวสร้างประเภทข้อมูล (data type constructors ) ที่กำหนด ฟังก์ชันแบบหนึ่งต่อหนึ่ง ( injective functions) จากทุกประเภทของชนิดใดชนิดหนึ่งไปยังชุดประเภทใหม่ และคำพ้องความหมายประเภท (type synonyms หรือtypedef ) ที่กำหนดฟังก์ชันจากทุกประเภทของชนิดใดชนิดหนึ่งไปยังชุดประเภทที่มีอยู่แล้วโดยใช้กรณีเดียว
ตระกูลประเภทและคลาสประเภทมีความสัมพันธ์กันอย่างใกล้ชิด: คลาสประเภทปกติจะกำหนดฟังก์ชันบางส่วนจากประเภทไปยังชุดของค่า ที่มีชื่อ โดยการจับคู่รูปแบบบนประเภทอินพุต ในขณะที่ตระกูลประเภทจะกำหนดฟังก์ชันบางส่วนจากประเภทไปยังประเภทโดยการจับคู่รูปแบบบนประเภทอินพุต อันที่จริง ในการใช้งานตระกูลประเภทหลายๆ ครั้ง จะมีคลาสประเภทเดียวที่ประกอบด้วยทั้งค่าและประเภทที่เกี่ยวข้องกับแต่ละอินสแตนซ์ในเชิงตรรกะ ตระกูลประเภทที่ประกาศภายในคลาสประเภทเรียกว่าประเภทที่เกี่ยวข้อง[ 2 ]
ภาษาโปรแกรมที่รองรับตระกูลประเภทหรือคุณสมบัติที่คล้ายกัน ได้แก่Haskell (ด้วยส่วนขยายภาษาทั่วไป) [ 3 ] Standard ML (ผ่านระบบโมดูล) [ 4 ] Rust [ 5 ] Scala (ภายใต้ชื่อ "ประเภทนามธรรม") [ 6 ] และ C ++ (ผ่านการใช้ typedefs ในเทมเพลต) [ 7 ]
การเปลี่ยนแปลง
ส่วนTypeFamiliesขยายในคอมไพเลอร์ Glasgow Haskellรองรับทั้งตระกูลคำพ้องความหมายประเภทและตระกูลข้อมูล ตระกูลคำพ้องความหมายประเภทเป็นรูปแบบที่ยืดหยุ่นกว่า (แต่ตรวจสอบประเภทได้ยากกว่า) ซึ่งอนุญาตให้ประเภทในโคโดเมนของฟังก์ชันประเภทเป็นประเภทใดก็ได้ที่มีชนิด ที่ เหมาะสม[ 7 ]ในทางกลับกัน ตระกูลข้อมูลจะจำกัดโคโดเมนโดยกำหนดให้แต่ละอินสแตนซ์ต้องกำหนดตัวสร้างประเภท ใหม่ สำหรับผลลัพธ์ของฟังก์ชัน ซึ่งทำให้มั่นใจได้ว่าฟังก์ชันนั้นเป็นแบบฉีด (injective)ทำให้บริบทของไคลเอ็นต์สามารถแยกส่วนตระกูลประเภทและรับประเภทอาร์กิวเมนต์ดั้งเดิมได้[ 1 ]
แรงจูงใจและตัวอย่าง
กลุ่มประเภท (Type families) มีประโยชน์ในการสร้างรูปแบบนามธรรมที่ "การจัดระเบียบ" หรือ "โครงสร้าง" ของประเภททั่วไปถูกนำมาใช้ซ้ำ แต่ใช้ประเภทเฉพาะที่แตกต่างกันในแต่ละครั้ง ตัวอย่างการใช้งานทั่วไป ได้แก่ การอธิบายประเภทข้อมูลนามธรรมเช่น คอลเลกชันทั่วไป หรือรูปแบบการออกแบบเช่นโมเดล-วิว-คอนโทรลเลอร์ (Model-View-Controller )
ประเภทข้อมูลนามธรรมที่ปรับปรุงตัวเองได้
หนึ่งในแรงจูงใจดั้งเดิมสำหรับการแนะนำประเภทที่เกี่ยวข้องคือการอนุญาตให้ประเภทข้อมูลนามธรรมสามารถกำหนดพารามิเตอร์โดยประเภทเนื้อหาเพื่อให้โครงสร้างข้อมูลที่ใช้ประเภทนามธรรมนั้นเปลี่ยนแปลงไปในลักษณะ "ปรับให้เหมาะสมด้วยตนเอง" [ 2 ] พารามิเตอร์ ประเภทข้อมูลพีชคณิตปกติสามารถอธิบายโครงสร้างข้อมูลที่มีพฤติกรรมสม่ำเสมอเมื่อเทียบกับประเภทอาร์กิวเมนต์ทั้งหมดเท่านั้น อย่างไรก็ตาม ประเภทที่เกี่ยวข้องสามารถอธิบายตระกูลของโครงสร้างข้อมูลที่มีอินเทอร์เฟซที่สม่ำเสมอ แต่แตกต่างกันในการใช้งานตามพารามิเตอร์ประเภทหนึ่งหรือมากกว่า ตัวอย่างเช่น[ 2 ]โดยใช้สัญกรณ์ประเภทที่เกี่ยวข้องของ Haskell เราสามารถประกาศคลาสประเภทของ ประเภทองค์ประกอบ อาร์เรย์ ที่ถูกต้อง โดยมีตระกูลข้อมูลที่เกี่ยวข้องแทนอาร์เรย์ของประเภทองค์ประกอบนั้น:
คลาสArrayElem e โดยที่ข้อมูลArray e ดัชนี:: Array e -> Int -> eจากนั้นจึงสามารถกำหนดอินสแตนซ์สำหรับคลาสนี้ได้ ซึ่งจะกำหนดทั้งโครงสร้างข้อมูลที่ใช้และการดำเนินการกับโครงสร้างข้อมูลในที่เดียว เพื่อประสิทธิภาพ เราอาจใช้ การแสดง เวกเตอร์บิตแบบ แพ็ค สำหรับอาร์เรย์ของ ค่า บูลีนในขณะที่ใช้โครงสร้างข้อมูลอาร์เรย์ ปกติ สำหรับค่าจำนวนเต็ม โครงสร้างข้อมูลสำหรับอาร์เรย์ของคู่ลำดับจะถูกกำหนดแบบเรียกซ้ำเป็นคู่ของอาร์เรย์ของแต่ละประเภทองค์ประกอบ
instance ArrayElem Bool where data Array Bool = BoolArray BitVector index ( BoolArray ar ) i = indexBitVector ar i instance ArrayElem Int where data Array Int = IntArray UIntArr index ( IntArray ar ) i = indexUIntArr ar i instance ( ArrayElem a , ArrayElem b ) => ArrayElem ( a , b ) where data Array ( a , b ) = PairArray ( Array a ) ( Array b ) index ( PairArray ar br ) = ( index ar i , index br i )ด้วยคำจำกัดความเหล่านี้ เมื่อลูกค้าอ้างอิงถึง ระบบArray (Int, Bool)จะเลือกการใช้งานโดยอัตโนมัติโดยใช้ตัวอย่างที่กำหนดไว้
คลาสสำหรับคอลเลกชัน
เมื่อกลับตัวอย่างก่อนหน้านี้ เรายังสามารถใช้ตระกูลประเภทเพื่อกำหนดคลาสสำหรับประเภทคอลเลกชัน โดยที่ฟังก์ชันประเภทจะแมปประเภทคอลเลกชันแต่ละประเภทไปยังประเภทองค์ประกอบที่สอดคล้องกัน: [ 7 ]
คลาสCollects c ที่ประเภทElem c ว่างเปล่า:: c แทรก:: Elem c -> c -> c toList :: c -> [ Elem c ] อินสแตนซ์Collects [ e ] ที่ประเภทElem [ e ] = e ว่างเปล่า= [] แทรก= ( : ) toList = id อินสแตนซ์ Ord e => Collects ( Set . Set e ) ที่ประเภทElem ( Set . Set e ) = e ว่างเปล่า= Set . ว่างเปล่าแทรก= Set . แทรกtoList = Set . toListในตัวอย่างนี้ การใช้ตระกูลคำพ้องความหมายประเภทแทนตระกูลข้อมูลมีความสำคัญอย่างยิ่ง เนื่องจากประเภทคอลเลกชันหลายประเภทอาจมีประเภทองค์ประกอบเดียวกันได้
การเปรียบเทียบกับความสัมพันธ์เชิงฟังก์ชัน
การพึ่งพาเชิงฟังก์ชันเป็นคุณลักษณะของระบบประเภทอีกอย่างหนึ่งที่มีการใช้งานคล้ายกับประเภทที่เชื่อมโยงกัน ในขณะที่ประเภทที่เชื่อมโยงกันจะเพิ่มฟังก์ชันประเภทที่มีชื่อซึ่งแมปพารามิเตอร์ของคลาสประเภทที่ครอบคลุมไปยังประเภทอื่น การพึ่งพาเชิงฟังก์ชันจะแสดงรายการประเภทผลลัพธ์เป็นพารามิเตอร์อีกตัวหนึ่งของคลาสประเภทและเพิ่มข้อจำกัดระหว่างพารามิเตอร์ประเภท (เช่น "พารามิเตอร์aกำหนดพารามิเตอร์b ได้อย่างเฉพาะ เจาะจง" เขียนว่า) การใช้งานการพึ่งพาเชิงฟังก์ชันที่พบบ่อยที่สุดสามารถแปลงเป็นประเภทที่เชื่อมโยงกันได้โดยตรงและในทางกลับกัน[ 7 ]a -> b
โดยทั่วไปแล้วตระกูลประเภทถือว่าตรวจสอบประเภทได้ง่ายกว่าการพึ่งพาเชิงฟังก์ชัน ข้อดีอีกประการหนึ่งของประเภทที่เกี่ยวข้องเหนือการพึ่งพาเชิงฟังก์ชันคือ การพึ่งพาเชิงฟังก์ชันนั้นต้องการให้ผู้ใช้คลาสประเภทระบุประเภทที่ขึ้นอยู่ทั้งหมดในบริบทของตน รวมถึงประเภทที่ไม่ได้ใช้ด้วย ในขณะที่ประเภทที่เกี่ยวข้องไม่ต้องการสิ่งนี้ การเพิ่มประเภทที่เกี่ยวข้องอีกประเภทหนึ่งลงในคลาสจึงต้องอัปเดตเฉพาะอินสแตนซ์ของคลาสเท่านั้น ในขณะที่ผู้ใช้ยังคงไม่เปลี่ยนแปลง ข้อดีหลักของการพึ่งพาเชิงฟังก์ชันเหนือตระกูลประเภทคือความยืดหยุ่นที่เพิ่มขึ้นในการจัดการกรณีที่ผิดปกติบางกรณี[ 8 ]
ลิงก์ภายนอก
- เอกสาร Haskell Wiki เกี่ยวกับการใช้ตระกูลประเภทใน GHC