Dem Typensystem von C # fehlen einige Funktionen, die zum ordnungsgemäßen Implementieren von Typklassen als Schnittstelle erforderlich sind.
Beginnen wir mit Ihrem Beispiel, aber der Schlüssel zeigt eine ausführlichere Darstellung dessen, was eine Typenklasse ist und tut, und versucht dann, diese C # -Bits zuzuordnen.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Dies ist die Typklassendefinition oder ähnlich der Schnittstelle. Betrachten wir nun die Definition eines Typs und die Implementierung dieser Typklasse.
data Awesome a = Awesome a a
instance Functor Awesome where
fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)
Jetzt können wir ganz offensichtlich eine eindeutige Tatsache von Typklassen erkennen, die Sie mit Interfaces nicht haben können. Die Implementierung der Typklasse ist nicht Bestandteil der Typdefinition. Um eine Schnittstelle in C # zu implementieren, müssen Sie sie als Teil der Definition des Typs implementieren, der sie implementiert. Dies bedeutet, dass Sie möglicherweise keine Schnittstelle für einen Typ implementieren, den Sie selbst nicht implementieren. In Haskell können Sie jedoch eine Typklasse für jeden Typ implementieren, auf den Sie Zugriff haben.
Das ist wahrscheinlich der größte sofort, aber es gibt einen weiteren ziemlich signifikanten Unterschied, der bewirkt, dass das C # -Äquivalent bei weitem nicht so gut funktioniert, und Sie berühren ihn in Ihrer Frage. Es geht um Polymorphismus. Außerdem gibt es einige relativ generische Dinge, die Haskell Ihnen ermöglicht, mit Typklassen umzugehen, die von vornherein nicht übersetzt werden, insbesondere wenn Sie sich mit dem Ausmaß des Generizismus in existenziellen Typen oder anderen GHC-Erweiterungen wie generischen ADTs befassen.
Sie sehen, mit Haskell können Sie die Funktoren definieren
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)
Dann können Sie im Verbrauch eine Funktion haben:
mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar
Hierin liegt das Problem. Wie schreibt man diese Funktion in 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>);
}
So gibt es ein paar Dinge falsch mit dem C # Version, für eine Sache , ich bin nicht einmal sicher , es erlaubt Ihnen , das zu verwenden , <b>
Qualifier , wie ich es tat, aber ohne es ich bin sicher , es würde nicht versenden Show<>
angemessen (fühlen sich frei , um zu versuchen , und kompilieren, um herauszufinden; habe ich nicht).
Das größere Problem hierbei ist jedoch, dass anders als oben in Haskell, wo wir unsere Terminal
s als Teil des Typs definiert und dann anstelle des Typs verwendbar hatten, weil C # keinen geeigneten parametrischen Polymorphismus aufweist (was sehr offensichtlich wird, sobald Sie versuchen, ein Interop durchzuführen F # mit C #) kann man nicht klar oder sauber unterscheiden, ob Rechts oder Links Terminal
s sind. Das Beste null
, was Sie tun können, ist die Verwendung , aber was ist, wenn Sie versuchen, einen Wert als Typ a festzulegen, Functor
oder wenn Sie Either
zwei Typen unterscheiden, die beide einen Wert enthalten? Jetzt müssen Sie einen Typ und zwei verschiedene Werte verwenden, um zu überprüfen und zwischen diesen zu wechseln, um Ihre Diskriminierung zu modellieren.
Das Fehlen geeigneter Summentypen, Vereinigungstypen und ADTs, wie auch immer Sie sie nennen möchten, führt dazu, dass viele der Typklassen verloren gehen, da Sie am Ende des Tages mehrere Typen (Konstruktoren) als einen Typ behandeln können. und das zugrunde liegende Typensystem von .NET hat einfach kein solches Konzept.