Ich glaube nicht, dass ich Typenklassen verstehe. Ich habe irgendwo gelesen, dass es falsch und irreführend ist, sich Typklassen als "Interfaces" (von OO) vorzustellen, die ein Typ implementiert. Das Problem ist, ich habe ein Problem damit, sie als etwas anderes zu sehen und wie das falsch ist.
Wenn ich zum Beispiel eine Typklasse habe (in Haskell-Syntax)
class Functor f where
fmap :: (a -> b) -> f a -> f b
Wie unterscheidet sich das von der Schnittstelle [1] (in Java-Syntax)
interface Functor<A> {
<B> Functor<B> fmap(Function<B, A> fn)
}
interface Function<Return, Argument> {
Return apply(Argument arg);
}
Ein möglicher Unterschied, den ich mir vorstellen kann, besteht darin, dass die Typklassenimplementierung, die bei einem bestimmten Aufruf verwendet wird, nicht spezifiziert, sondern aus der Umgebung bestimmt wird - beispielsweise durch Prüfen der verfügbaren Module für eine Implementierung für diesen Typ. Das scheint ein Implementierungsartefakt zu sein, das in einer OO-Sprache angesprochen werden könnte. wie der Compiler (oder die Laufzeit) nach einem Wrapper / Extender / Monkey-Patcher suchen könnte, der die erforderliche Schnittstelle für den Typ verfügbar macht.
Was vermisse ich?
[1] Beachten Sie, dass das f a
Argument entfernt wurde, fmap
da Sie diese Methode für ein Objekt aufrufen würden, da es sich um eine OO-Sprache handelt. Diese Schnittstelle setzt voraus, dass das f a
Argument behoben wurde.
C
ohne die Anwesenheit von Unterdrückten?Beachten Sie neben Andreas 'hervorragender Antwort auch, dass Typklassen das Überladen optimieren sollen , was sich auf den globalen Namensraum auswirkt. In Haskell gibt es keine andere Überladung als die, die Sie über Typklassen erhalten können. Wenn Sie dagegen Objektschnittstellen verwenden, müssen sich nur die Funktionen, für die die Argumente dieser Schnittstelle deklariert sind, um die Funktionsnamen in dieser Schnittstelle kümmern. Schnittstellen stellen also lokale Namensräume bereit.
Zum Beispiel hatten Sie
fmap
in einer Objektschnittstelle namens "Functor". Es wäre vollkommen in Ordnung, einen anderenfmap
in einer anderen Schnittstelle zu haben, sagen wir "Structor". Jedes Objekt (oder jede Klasse) kann auswählen, welche Schnittstelle implementiert werden soll. Im Gegensatz dazu können Sie in Haskell nur einenfmap
in einem bestimmten Kontext haben. Sie können nicht gleichzeitig die Typklassen Functor und Structor in denselben Kontext importieren.Objektschnittstellen ähneln eher Standard-ML-Signaturen als Typklassen.
quelle
In Ihrem konkreten Beispiel (mit Functor-Typklasse) verhalten sich Haskell- und Java-Implementierungen unterschiedlich. Stellen Sie sich vor, Sie haben den Datentyp "Vielleicht" und möchten, dass er Functor ist (ein in Haskell sehr beliebter Datentyp, den Sie auch in Java problemlos implementieren können). In Ihrem Java-Beispiel werden Sie die Klasse Maybe veranlassen, Ihre Functor-Schnittstelle zu implementieren. Sie können also Folgendes schreiben (nur Pseudocode, da ich nur C # -Hintergrund habe):
Beachten Sie, dass der
res
Typ Functor ist, nicht Vielleicht. Dies macht die Java-Implementierung nahezu unbrauchbar, da Sie konkrete Typinformationen verlieren und Casts durchführen müssen. (Zumindest konnte ich eine solche Implementierung nicht schreiben, bei der noch Typen vorhanden waren). Bei Haskell-Typklassen erhalten Sie als Ergebnis "Maybe Int".quelle
Maybe<Int>
.