Javas Schnittstelle und Haskells Typklasse: Unterschiede und Ähnlichkeiten?


110

Während ich Haskell lerne, habe ich seine Typklasse bemerkt , die eine großartige Erfindung sein soll, die von Haskell stammt.

Auf der Wikipedia-Seite zur Typklasse :

Der Programmierer definiert eine Typklasse, indem er eine Reihe von Funktions- oder Konstantennamen zusammen mit ihren jeweiligen Typen angibt, die für jeden Typ vorhanden sein müssen, der zur Klasse gehört.

Das scheint mir ziemlich nah an Javas Schnittstelle zu sein (zitiert die Java-Seite von Wikipedia ):

Eine Schnittstelle in der Programmiersprache Java ist ein abstrakter Typ, mit dem eine Schnittstelle (im allgemeinen Sinne des Begriffs) angegeben wird, die Klassen implementieren müssen.

Diese beiden sehen sich ziemlich ähnlich: Typklassen beschränken das Verhalten eines Typs, während Schnittstellen das Verhalten einer Klasse einschränken.

Ich frage mich, was die Unterschiede und Ähnlichkeiten zwischen der Typklasse in Haskell und der Schnittstelle in Java sind oder ob sie sich grundlegend unterscheiden.

EDIT: Mir ist aufgefallen, dass sogar haskell.org zugibt, dass sie ähnlich sind . Wenn sie sich so ähnlich sind (oder?), Warum wird dann die Typklasse mit einem solchen Hype behandelt?

MEHR BEARBEITEN: Wow, so viele tolle Antworten! Ich denke, ich muss die Community entscheiden lassen, welches das beste ist. Beim Lesen der Antworten scheinen alle nur zu sagen, dass "es viele Dinge gibt, die Typklassen tun können, während die Benutzeroberfläche nicht mit Generika umgehen kann oder muss" . Ich kann nicht anders, als mich zu fragen, ob Schnittstellen etwas tun können, während Typklassen dies nicht können. Außerdem habe ich festgestellt, dass Wikipedia behauptet, dass die Typklasse ursprünglich in der Veröffentlichung von 1989 * "Wie man Ad-hoc-Polymorphismus weniger ad hoc macht" erfunden wurde, während Haskell noch in der Wiege steckt, während das Java-Projekt 1991 gestartet und 1995 erstmals veröffentlicht wurde Also. vielleicht statt typeclass ähnlich wie Schnittstellen zu sein, seine umgekehrt, dass Schnittstellen von typeclass beeinflusst wurde?Gibt es Dokumente / Papiere, die dies unterstützen oder widerlegen? Vielen Dank für alle Antworten, sie sind alle sehr aufschlussreich!

Danke für alle Eingaben!


3
Nein, es gibt nicht wirklich alles, was Schnittstellen tun können, was Typklassen nicht können. Die größte Einschränkung besteht darin, dass Schnittstellen im Allgemeinen in Sprachen mit integrierten Funktionen angezeigt werden, die in Haskell nicht vorhanden sind. Wenn Java Typklassen hinzugefügt würden, könnten sie diese Funktionen ebenfalls verwenden.
CA McCann

8
Wenn Sie mehrere Fragen haben, sollten Sie mehrere Fragen stellen und nicht versuchen, alle in eine Frage zu packen. Um Ihre letzte Frage zu beantworten: Javas Haupteinfluss ist Objective-C (und nicht C ++, wie oft fälschlicherweise berichtet wird), dessen Haupteinflüsse wiederum Smalltalk und C sind. Javas Schnittstellen sind eine Anpassung der Protokolle von Objective-C , die wiederum a sind Formalisierung der Idee des Protokolls in OO, die wiederum auf der Idee von Protokollen in Netzwerken basiert , insbesondere im ARPANet. Dies alles geschah lange vor dem von Ihnen zitierten Artikel. ...
Jörg W Mittag

1
... Haskells Einfluss auf Java kam viel später und beschränkt sich auf Generika, die schließlich von einem der Designer von Haskell, Phil Wadler, mitgestaltet wurden.
Jörg W Mittag

5
Dies ist ein Usenet-Artikel von Patrick Naughton, einem der ursprünglichen Designer von Java: Java wurde stark von Objective-C und nicht von C ++ beeinflusst . Leider ist es so alt, dass das ursprüngliche Posting nicht einmal in den Archiven von Google erscheint.
Jörg W Mittag

8
Es gibt eine andere Frage, die als genaues Duplikat dieser Frage geschlossen wurde, aber eine viel ausführlichere Antwort enthält: stackoverflow.com/questions/8122109/…
Ben

Antworten:


48

Ich würde sagen, dass eine Schnittstelle wie eine Typklasse ist, SomeInterface tin der alle Werte den Typ haben t -> whatever(wo whatevernicht enthalten t). Dies liegt daran, dass bei der Art der Vererbungsbeziehung in Java und ähnlichen Sprachen die aufgerufene Methode von der Art des Objekts abhängt, für das sie aufgerufen werden, und von nichts anderem.

Das bedeutet, dass es sehr schwierig ist, Dinge wie add :: t -> t -> tmit einer Schnittstelle zu erstellen, bei der sie für mehr als einen Parameter polymorph ist, da die Schnittstelle nicht angeben kann, dass der Argumenttyp und der Rückgabetyp der Methode vom Typ sind das Objekt, auf das es aufgerufen wird (dh der Typ "Selbst"). Mit Generics gibt es einige Möglichkeiten, dies zu fälschen, indem Sie eine Schnittstelle mit generischen Parametern erstellen, von denen erwartet wird, dass sie vom selben Typ wie das Objekt selbst sind, wie es Comparable<T>funktioniert, wo Sie sie verwenden sollen, Foo implements Comparable<Foo>damit die compareTo(T otherobject)Art von Typ hat t -> t -> Ordering. Dies erfordert jedoch weiterhin, dass der Programmierer diese Regel befolgt, und verursacht auch Kopfschmerzen, wenn Benutzer eine Funktion erstellen möchten, die diese Schnittstelle verwendet, müssen sie rekursive generische Typparameter haben.

Außerdem werden Sie keine Dinge haben, empty :: tweil Sie hier keine Funktion aufrufen, es ist also keine Methode.


1
Scala-Merkmale (im Grunde Schnittstellen) ermöglichen diesen Typ, sodass Sie Parameter des "Selbsttyps" zurückgeben oder akzeptieren können. Scala hat eine völlig separate Funktion, die sie übrigens "Selbsttypen" nennen und die nichts damit zu tun hat. Nichts davon ist ein konzeptioneller Unterschied, nur ein Implementierungsunterschied.
Ton

44

Was zwischen Schnittstellen und Typklassen ähnlich ist, ist, dass sie eine Reihe verwandter Operationen benennen und beschreiben. Die Operationen selbst werden über ihre Namen, Ein- und Ausgänge beschrieben. Ebenso kann es viele Implementierungen dieser Operationen geben, die sich wahrscheinlich in ihrer Implementierung unterscheiden.

Hier sind einige bemerkenswerte Unterschiede:

  • Schnittstellenmethoden sind immer einer Objektinstanz zugeordnet. Mit anderen Worten, es gibt immer einen impliziten 'this'-Parameter, der das Objekt ist, für das die Methode aufgerufen wird. Alle Eingaben in eine Typklassenfunktion sind explizit.
  • Eine Schnittstellenimplementierung muss als Teil der Klasse definiert werden, die die Schnittstelle implementiert. Umgekehrt kann eine Typklassen-Instanz vollständig getrennt von ihrem zugeordneten Typ definiert werden ... auch in einem anderen Modul.

Im Allgemeinen denke ich, dass es fair ist zu sagen, dass Typklassen leistungsfähiger und flexibler sind als Schnittstellen. Wie würden Sie eine Schnittstelle zum Konvertieren einer Zeichenfolge in einen Wert oder eine Instanz des Implementierungstyps definieren? Es ist sicherlich nicht unmöglich, aber das Ergebnis wäre nicht intuitiv oder elegant. Haben Sie sich jemals gewünscht, eine Schnittstelle für einen Typ in einer kompilierten Bibliothek implementieren zu können? Beides ist mit Typklassen leicht zu erreichen.


1
Wie würden Sie Typenklassen erweitern? Können Typklassen andere Typklassen erweitern, genauso wie Schnittstellen Schnittstellen erweitern können?
CMCDragonkai

10
Es könnte sich lohnen, diese Antwort angesichts der Standardimplementierungen von Java 8 in Schnittstellen zu aktualisieren.
Stellen Sie Monica am

1
@CMCDragonkai Ja, Sie können beispielsweise "class (Foo a) => Bar a where ..." sagen, um anzugeben, dass die Bar-Typklasse die Foo-Typklasse erweitert. Wie Java hat auch Haskell hier eine Mehrfachvererbung.
Stellen Sie Monica am

Ist in diesem Fall die Typklasse nicht mit den Protokollen in Clojure identisch, jedoch mit Typensicherheit?
Nawfal

24

Typklassen wurden als strukturierte Ausdrucksweise für "Ad-hoc-Polymorphismus" erstellt, der im Grunde der Fachbegriff für überladene Funktionen ist . Eine Typklassendefinition sieht ungefähr so ​​aus:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Dies bedeutet, dass beim Anwenden der Funktion fooauf einige Argumente eines Typs, der zur Klasse gehört Foobar, eine foofür diesen Typ spezifische Implementierung nachgeschlagen und verwendet wird. Dies ist der Situation mit Operatorüberladung in Sprachen wie C ++ / C # sehr ähnlich, außer dass sie flexibler und allgemeiner ist.

Schnittstellen dienen in OO-Sprachen einem ähnlichen Zweck, aber das zugrunde liegende Konzept ist etwas anders. OO-Sprachen verfügen über eine integrierte Vorstellung von Typhierarchien, die Haskell einfach nicht hat, was die Sache in gewisser Weise kompliziert, da Schnittstellen sowohl eine Überladung durch Subtypisierung beinhalten können (dh Methoden auf geeigneten Instanzen aufrufen, Subtypen, die Schnittstellen implementieren, die ihre Supertypen tun). und durch flachen typbasierten Versand (da zwei Klassen, die eine Schnittstelle implementieren, möglicherweise keine gemeinsame Oberklasse haben, die sie auch implementiert). Angesichts der enormen zusätzlichen Komplexität, die durch die Subtypisierung entsteht, empfehle ich, Typklassen als eine verbesserte Version überladener Funktionen in einer Nicht-OO-Sprache zu betrachten.

Erwähnenswert ist auch, dass Typklassen wesentlich flexiblere Versandmöglichkeiten haben - Schnittstellen gelten im Allgemeinen nur für die einzelne Klasse, die sie implementiert, während Typklassen für einen Typ definiert sind, der an einer beliebigen Stelle in der Signatur der Klassenfunktionen erscheinen kann. Das Äquivalent dazu in OO-Schnittstellen wäre, dass die Schnittstelle Möglichkeiten definiert, ein Objekt dieser Klasse an andere Klassen zu übergeben, statische Methoden und Konstruktoren zu definieren, die eine Implementierung basierend auf dem im Aufrufkontext erforderlichen Rückgabetyp auswählen und Methoden definieren, die Nehmen Sie Argumente des gleichen Typs wie die Klasse, die die Schnittstelle implementiert, und verschiedene andere Dinge, die überhaupt nicht wirklich übersetzt werden.

Kurz gesagt: Sie dienen ähnlichen Zwecken, aber ihre Arbeitsweise ist etwas anders, und Typklassen sind sowohl wesentlich aussagekräftiger als auch in einigen Fällen einfacher zu verwenden, da an festen Typen gearbeitet wird und nicht an Teilen einer Vererbungshierarchie.


Ich hatte Probleme, Typhierarchien in Haskell zu verstehen. Was halten Sie von Typsystemen wie Omega? Könnten sie Typhierarchien simulieren?
CMCDragonkai

@CMCDragonkai: Ich kenne Omega nicht genug, um wirklich zu sagen, sorry.
CA McCann

16

Ich habe die obigen Antworten gelesen. Ich habe das Gefühl, etwas klarer antworten zu können:

Eine Haskell "Typklasse" und eine Java / C # "Schnittstelle" oder ein Scala "Merkmal" sind grundsätzlich analog. Es gibt keine konzeptionelle Unterscheidung zwischen ihnen, aber es gibt Implementierungsunterschiede:

  • Haskell-Typklassen werden mit "Instanzen" implementiert, die von der Datentypdefinition getrennt sind. In C # / Java / Scala müssen die Schnittstellen / Merkmale in der Klassendefinition implementiert werden.
  • Mit Haskell-Typklassen können Sie einen Typ oder einen Selbsttyp zurückgeben. Scala-Merkmale auch (this.type). Beachten Sie, dass "Selbsttypen" in Scala eine völlig unabhängige Funktion sind. Java / C # erfordert eine unübersichtliche Problemumgehung mit Generika, um dieses Verhalten zu approximieren.
  • Mit Haskell-Typklassen können Sie Funktionen (einschließlich Konstanten) definieren, ohne den Parameter "this" einzugeben. Java / C # -Schnittstellen und Scala-Merkmale erfordern für alle Funktionen einen Eingabeparameter "this".
  • Mit Haskell-Typklassen können Sie Standardimplementierungen für Funktionen definieren. Dies gilt auch für Scala-Merkmale und Java 8+ -Schnittstellen. C # kann mit Erweiterungsmethoden so etwas annähern.

2
Um diesen Antwortpunkten etwas Literatur hinzuzufügen, habe ich heute On (Haskell) Type Classes und (C #) Interfaces gelesen , die auch mit C # -Schnittstellen anstelle von Javas verglichen werden, aber auf der konzeptionellen Seite ein Verständnis vermitteln sollten, das den Begriff a zerlegt Schnittstelle über Sprachgrenzen hinweg.
daniel.kahlenberg

Ich denke, einige Annäherungen für Dinge wie Standardimplementierungen werden in C # mit abstrakten Klassen vielleicht effizienter durchgeführt.
Arwin

12

In Master Minds of Programming gibt es ein Interview über Haskell mit Phil Wadler, dem Erfinder der Typklassen, der die Ähnlichkeiten zwischen Schnittstellen in Java und Typklassen in Haskell erklärt:

Eine Java-Methode wie:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

ist der Haskell-Methode sehr ähnlich:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Typklassen beziehen sich also auf Schnittstellen, aber die tatsächliche Entsprechung wäre eine statische Methode, die mit einem Typ wie oben parametrisiert wird.


Dies sollte die Antwort sein, da er laut Phil Wadlers Homepage der Hauptdesigner von Haskell war und gleichzeitig die Generics-Erweiterung für Java entwarf, die später in der Sprache selbst enthalten war.
Wong Jia Hau


8

Lesen Sie Softwareerweiterung und Integration in Typklassen. Hier finden Sie Beispiele dafür, wie Typklassen eine Reihe von Problemen lösen können, die Schnittstellen nicht können.

Beispiele in der Arbeit aufgeführt sind:

  • das Ausdrucksproblem,
  • das Problem der Framework-Integration,
  • das Problem der unabhängigen Erweiterbarkeit,
  • die Tyrannei der dominanten Zersetzung, Streuung und Verwicklung.

3
Link ist oben tot. Versuchen Sie stattdessen dieses .
Justin Leitgeb

1
Nun, da dieser auch tot ist. Versuchen Sie dieses
RichardW

7

Ich kann nicht mit dem "Hype" -Niveau sprechen, wenn es so gut scheint. Aber ja, Typklassen sind in vielerlei Hinsicht ähnlich. Ein Unterschied, den ich mir vorstellen kann, ist, dass Sie mit Haskell Verhalten für einige Operationen der Typklasse bereitstellen können :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

Dies zeigt, dass es zwei Operationen gibt, gleich (==)und ungleich (/=), für Dinge, die Instanzen der EqTypklasse sind. Die ungleiche Operation wird jedoch als gleich definiert (sodass Sie nur eine angeben müssen) und umgekehrt.

In wahrscheinlich nicht legalem Java wäre das so etwas wie:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

und die Art und Weise, wie es funktionieren würde, ist, dass Sie nur eine dieser Methoden bereitstellen müssten, um die Schnittstelle zu implementieren. Ich würde also sagen, dass die Fähigkeit, eine Art Teilimplementierung des gewünschten Verhaltens auf Schnittstellenebene bereitzustellen , einen Unterschied darstellt.


6

Sie sind ähnlich (lesen Sie: haben eine ähnliche Verwendung) und werden wahrscheinlich ähnlich implementiert: Polymorphe Funktionen in Haskell nehmen unter der Haube eine 'vtable' auf, in der die mit der Typklasse verbundenen Funktionen aufgelistet sind.

Diese Tabelle kann häufig zur Kompilierungszeit abgeleitet werden. Dies trifft in Java wahrscheinlich weniger zu.

Dies ist jedoch eine Tabelle mit Funktionen , keine Methoden . Methoden sind an ein Objekt gebunden, Haskell-Typklassen nicht.

Sehen Sie sie eher wie Javas Generika.


3

Wie Daniel sagt, werden Schnittstellenimplementierungen definiert separat Erklärungen von Daten. Und wie andere bereits betont haben, gibt es eine einfache Möglichkeit, Operationen zu definieren, die denselben freien Typ an mehr als einer Stelle verwenden. Es ist also einfach Numals Typklasse zu definieren . So erhalten wir in Haskell die syntaktischen Vorteile der Überladung von Operatoren, ohne dass magische Operatoren überladen sind - nur Standardtypklassen.

Ein weiterer Unterschied besteht darin, dass Sie Methoden verwenden können, die auf einem Typ basieren, auch wenn Sie noch keinen konkreten Wert dieses Typs haben!

Zum Beispiel read :: Read a => String -> a. Wenn Sie also genügend andere Typinformationen darüber haben, wie Sie das Ergebnis eines "Lesens" verwenden, können Sie den Compiler herausfinden lassen, welches Wörterbuch für Sie verwendet werden soll.

Sie können auch Dinge tun, mit instance (Read a) => Read [a] where...denen Sie eine Leseinstanz für definieren können beliebige Liste lesbarer Dinge definieren können. Ich denke nicht, dass das in Java gut möglich ist.

Und das alles sind nur Standard-Einzelparameter-Typklassen ohne Tricks. Sobald wir Typklassen mit mehreren Parametern einführen, eröffnet sich eine völlig neue Welt von Möglichkeiten, insbesondere mit funktionalen Abhängigkeiten und Typfamilien, mit denen Sie viel mehr Informationen und Berechnungen in das Typsystem einbetten können.


1
Normale Typklassen beziehen sich auf Schnittstellen, während Typklassen mit mehreren Parametern in OOP mehrfach versendet werden sollen. Sie erhalten eine entsprechende Steigerung der Leistung sowohl der Programmiersprache als auch der Kopfschmerzen des Programmierers.
CA McCann
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.