ระบบชนิด C # ขาดคุณสมบัติสองอย่างที่จำเป็นในการใช้คลาสประเภทเป็นอินเทอร์เฟซอย่างเหมาะสม
มาเริ่มด้วยตัวอย่างของคุณกัน แต่กุญแจสำคัญคือการแสดงบัญชีที่สมบูรณ์ยิ่งขึ้นว่า typeclass คืออะไรและทำอะไรจากนั้นลองแมปเหล่านั้นกับ C # bits
class Functor f where
fmap :: (a -> b) -> f a -> f b
นี่คือนิยามคลาสชนิดหรือคล้ายกับอินเตอร์เฟส ทีนี้ลองดูคำจำกัดความของประเภทและการนำไปใช้ของคลาสประเภทนั้น
data Awesome a = Awesome a a
instance Functor Awesome where
fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)
ตอนนี้เราสามารถเห็นความจริงที่แตกต่างของคลาสชนิดที่คุณไม่สามารถมีกับอินเตอร์เฟส การใช้งานของคลาสชนิดไม่ได้เป็นส่วนหนึ่งของคำจำกัดความของชนิด ใน C # เพื่อใช้อินเทอร์เฟซคุณต้องใช้มันเป็นส่วนหนึ่งของคำจำกัดความของประเภทที่ใช้มัน ซึ่งหมายความว่าคุณไม่สามารถใช้อินเทอร์เฟซสำหรับประเภทที่คุณไม่ได้ใช้ด้วยตนเองอย่างไรก็ตามใน Haskell คุณสามารถใช้คลาสประเภทสำหรับประเภทใดก็ได้ที่คุณเข้าถึง
นั่นอาจใหญ่ที่สุดในทันที แต่มีความแตกต่างที่สำคัญพอสมควรที่ทำให้ค่า C # นั้นใช้งานได้ไม่ดีนักและคุณก็สัมผัสกับมันในคำถามของคุณ มันเกี่ยวกับความแตกต่าง นอกจากนี้ยังมีบางสิ่งที่ค่อนข้างสามัญ Haskell ช่วยให้คุณสามารถทำกับคลาสประเภทที่ไม่แปลโดยเฉพาะอย่างยิ่งเมื่อคุณเริ่มดูจำนวนนิยมในประเภทอัตถิภาวนิยมหรือส่วนขยาย GHC อื่น ๆ เช่น Generic ADTs
คุณเห็นด้วย Haskell คุณสามารถกำหนดฟังก์ชั่น
data List a = List a (List a) | Terminal
data Tree a = Tree val (Tree a) (Tree a) | Terminal
instance Functor List where
fmap :: (a -> b) -> List a -> List b
fmap f (List a Terminal) = List (f a) Terminal
fmap f (List a rest) = List (f a) (fmap f rest)
instance Functor Tree where
fmap :: (a -> b) -> Tree a -> Tree b
fmap f (Tree val Terminal Terminal) = Tree (f val) Terminal Terminal
fmap f (Tree val Terminal right) = Tree (f val) Terminal (fmap f right)
fmap f (Tree val left Terminal) = Tree (f val) (fmap f left) Terminal
fmap f (Tree val left right) = Tree (f val) (fmap f left) (fmap f right)
จากนั้นในการบริโภคคุณสามารถมีฟังก์ชั่น:
mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar
ในที่นี้ปัญหาอยู่ ใน C # คุณจะเขียนฟังก์ชั่นนี้ได้อย่างไร?
public Tree<a> : Functor<a>
{
public a Val { get; set; }
public Tree<a> Left { get; set; }
public Tree<a> Right { get; set; }
public Functor<b> fmap<b>(Func<a,b> f)
{
return new Tree<b>
{
Val = f(val),
Left = Left.fmap(f);
Right = Right.fmap(f);
};
}
}
public string Show<a>(Showwable<a> ror)
{
return ror.Show();
}
public Functor<String> mapsSomething<a,b>(Functor<a> rar) where a : Showwable<b>
{
return rar.fmap(Show<b>);
}
ดังนั้นมีความผิดสิ่งที่คู่กับรุ่น C # สำหรับสิ่งหนึ่งที่ผมไม่ได้แน่ใจว่ามันจะช่วยให้คุณใช้<b>
รอบคัดเลือกเช่นผมมี แต่ไม่ว่าผมรู้สึกบางอย่างมันจะไม่ส่งShow<>
อย่างเหมาะสม (รู้สึกอิสระที่จะลอง และรวบรวมเพื่อค้นหาฉันไม่ได้)
ปัญหาที่ใหญ่กว่าที่นี่คือที่แตกต่างจากใน Haskell ที่เราได้Terminal
กำหนดไว้เป็นส่วนหนึ่งของประเภทและจากนั้นใช้งานได้แทนประเภทเนื่องจาก C # ขาด polamorphism ที่เหมาะสม (ซึ่งกลายเป็นซุปเปอร์ชัดเจนทันทีที่คุณพยายาม interop F # กับ C #) คุณไม่สามารถแยกแยะได้อย่างชัดเจนหรือชัดเจนว่าทางขวาหรือซ้ายเป็นTerminal
ของ ดีที่สุดที่คุณสามารถทำได้คือใช้null
แต่จะทำอย่างไรถ้าคุณกำลังพยายามสร้างค่าประเภท a Functor
หรือในกรณีEither
ที่คุณกำลังแยกความแตกต่างสองประเภทที่ทั้งสองมีค่า ตอนนี้คุณต้องใช้หนึ่งประเภทและมีสองค่าที่แตกต่างกันในการตรวจสอบและสลับไปเป็นแบบจำลองการเลือกปฏิบัติของคุณหรือไม่
การขาดประเภทผลรวมที่เหมาะสมประเภทสหภาพ ADT สิ่งที่คุณต้องการเรียกพวกเขาทำให้สิ่งที่พิมพ์ดีดออกมามากมายทำให้คุณตกหล่นเพราะในตอนท้ายของวันที่พวกเขาให้คุณปฏิบัติหลายประเภท (คอนสตรัคเตอร์) เป็นประเภทเดียว และระบบพิมพ์พื้นฐานของ. NET นั้นไม่มีแนวคิดดังกล่าว