Ich versuche, Haskells Typklassen und C # -Schnittstellen zu vergleichen. Angenommen, es gibt eine Functor
.
Haskell:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Wie implementiere ich diese Typklasse als Schnittstelle in C #?
Was ich ausprobiert habe:
interface Functor<A, B>
{
F<B> fmap(Func<A, B> f, F<A> x);
}
Dies ist eine ungültige Implementierung und ich bin tatsächlich mit generischen F
Typen festgefahren , die von zurückgegeben werden sollten fmap
. Wie und wo soll es definiert werden?
Ist es unmöglich, Functor
in C # zu implementieren und warum? Oder gibt es vielleicht einen anderen Ansatz?
Antworten:
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.
Dies ist die Typklassendefinition oder ähnlich der Schnittstelle. Betrachten wir nun die Definition eines Typs und die Implementierung dieser Typklasse.
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
Dann können Sie im Verbrauch eine Funktion haben:
Hierin liegt das Problem. Wie schreibt man diese Funktion in C #?
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 versendenShow<>
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 LinksTerminal
s sind. Das Bestenull
, was Sie tun können, ist die Verwendung , aber was ist, wenn Sie versuchen, einen Wert als Typ a festzulegen,Functor
oder wenn SieEither
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.
quelle
Was Sie brauchen, sind zwei Klassen, eine zur Modellierung des Generikums höherer Ordnung (des Funktors) und eine zur Modellierung des kombinierten Funktors mit dem freien Wert A
Wenn wir also die Option monad verwenden (weil alle Monaden Funktoren sind)
Sie können dann statische Erweiterungsmethoden verwenden, um bei Bedarf von IF <Option, B> nach Some <A> zu konvertieren
quelle
pure
der generischen Funktionsoberfläche: Der Compiler beschwert sich überIF<Functor, A> pure<A>(A a);
"Der TypFunctor
kann nicht als TypparameterFunctor
in der generischen Methode verwendet werdenIF<Functor, A>
. Es findet keine Boxing-Konvertierung oder Typparameter-Konvertierung vonFunctor
nach stattF<Functor>
." Was bedeutet das? Und warum müssen wirpure
an zwei Stellen definieren ? Darüber hinaus sollte nichtpure
statisch sein?