У системі типів C # немає декількох функцій, необхідних для належної реалізації класів типів як інтерфейсу.
Почнемо з вашого прикладу, але ключ показує більш повний виклад того, що таке тип класу і що робиться, а потім намагається зіставити їх на C # біти.
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 ADT.
Розумієте, за допомогою 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 # відповідного параметричного поліморфізму (що стає надзвичайно очевидним, як тільки ви намагаєтесь інтеропувати F # з C #) ви не можете чітко чи чітко розрізнити, чи Право чи Ліво Terminal
s. Найкраще, що ви можете зробити, це використовувати null
, але що робити , якщо ви намагаєтеся створити тип значення a Functor
або у випадку, Either
коли ви розрізняєте два типи, які мають обоє значення? Тепер ви повинні використовувати один тип і мати два різних значення для перевірки та переключення між моделями вашої дискримінації?
Відсутність належних типів суми, типів об'єднань, ADT, як би ви їх не хотіли називати, дуже багато з того, що типи класів дають вам відпасти, оскільки в кінці дня вони дозволяють вам ставитися до декількох типів (конструкторів) як до одного типу, та система базового типу .NET просто не має такої концепції.