Gründe für das Entfernen von Funktionstypen in Java 8

12

Ich habe versucht zu verstehen, warum die JDK 8 Lambda Expert Group (EG) beschlossen hat, keinen neuen Funktionstyp in die Java-Programmiersprache aufzunehmen.

Beim Durchgehen der Mailingliste fand ich einen Thread mit der Diskussion über das Entfernen von Funktionstypen .

Viele der Aussagen sind für mich zweideutig, möglicherweise wegen des Mangels an Kontext und in einigen Fällen wegen meines begrenzten Wissens über die Implementierung von Typensystemen.

Es gibt jedoch einige Fragen, die ich meiner Meinung nach sicher auf dieser Website formulieren kann, damit ich besser verstehe, was sie bedeuten.

Ich weiß, dass ich die Fragen in der Mailingliste stellen könnte, aber der Thread ist alt und alle Entscheidungen sind bereits getroffen, so dass die Wahrscheinlichkeit groß ist, dass ich ignoriert werde, vor allem, dass diese Jungs bereits mit ihren Plänen in Verzug geraten.

In seiner Antwort, die Entfernung von Funktionstypen zu unterstützen und die Verwendung von SAM-Typen zu befürworten, sagt Brian Goetz:

Keine Bestätigung. Es gab einen langen Thread darüber, wie nützlich es sein würde, Funktionstypen zu reifizieren. Ohne Reifizierung werden Funktionstypen gehumpelt.

Ich konnte den Thread, den er erwähnt, nicht finden. Jetzt kann ich verstehen, dass die Einführung eines strukturellen Funktionstyps gewisse Komplikationen im Java-System mit zumeist nominalen Typen mit sich bringen kann. Was ich nicht verstehen kann, ist, wie sich parametrisierte SAM-Typen in Bezug auf die Reifizierung unterscheiden.

Haben nicht beide die gleichen Probleme bei der Wiedervereinigung? Versteht jemand, wie sich Funktionstypen von parametrisierten SAM-Typen in Bezug auf die Reifizierung unterscheiden?

In einem anderen Kommentar sagt Goetz:

Es gibt zwei grundlegende Ansätze für die Typisierung: nominal und strukturell. Die Identität eines Nominalen basiert auf seinem Namen; Die Identität eines Strukturtyps hängt davon ab, woraus er besteht (z. B. "Tupel von int, int" oder "Funktion von int bis float"). Die meisten Sprachen wählen meistens nominale oder meistens strukturelle; Es gibt nicht viele Sprachen, in denen die nominelle und die strukturelle Typisierung mit Ausnahme von "um die Kanten" erfolgreich gemischt werden. Java ist fast vollständig nominell (mit wenigen Ausnahmen: Arrays sind ein Strukturtyp, aber am Ende steht immer ein nomineller Elementtyp; Generika haben auch eine Mischung aus nominell und strukturell, und dies ist in der Tat ein Teil der Quelle vieler von Beschwerden der Menschen über Generika.) Pfropfen eines Strukturtypsystems (Funktionstypen) auf Java ' Das nominelle Typensystem bedeutet neue Komplexität und Randfälle. Lohnt sich der Nutzen von Funktionstypen?

Diejenigen von Ihnen, die Erfahrung in der Implementierung von Typsystemen haben. Kennen Sie Beispiele für diese Komplexität oder Randfälle, die er hier erwähnt?

Ehrlich gesagt, verwirren mich diese Behauptungen, wenn ich bedenke, dass eine Programmiersprache wie Scala, die vollständig auf der JVM basiert, strukturelle Typen wie Funktionen und Tupel unterstützt, selbst bei den Reifizierungsproblemen der zugrunde liegenden Plattform.

Verstehen Sie mich nicht falsch, ich sage nicht, dass ein Funktionstyp besser sein sollte als die SAM-Typen. Ich möchte nur verstehen, warum sie diese Entscheidung getroffen haben.

edalorzo
quelle
4
"Das Übertragen eines Strukturtypsystems (Funktionstypen) auf das nominelle Typsystem von Java bedeutet eine neue Komplexität" - dies war höchstwahrscheinlich als Komplexität für Benutzer gedacht , die mehr lernen müssen, um die Sprache zu verwenden . So etwas wie einfaches und leicht zu bedienendes Java wird so komplex und schwer zu erlernen wie C ++ . Mitglieder der Mailing-Liste könnten wahrscheinlich auf Kritik an der vorherigen "großen Welle von Verbesserungen"
gnat,
3
"Einfach und leicht zu bedienendes Java wird zu komplex und zu schwer zu erlernen." Leider besteht ein Problem bei gängigen Programmiersprachen darin, dass sie die Abwärtskompatibilität beibehalten müssen und daher tendenziell zu komplex werden. IMHO (mit langjähriger Erfahrung sowohl mit C ++ als auch mit Java) gibt es also etwas Wahres an dieser Aussage. Ich habe oft das Gefühl, dass Java eingefroren und stattdessen eine neue Sprache entwickelt werden sollte. Oracle kann ein solches Risiko jedoch nicht eingehen.
Giorgio
4
@edalorzo: Wie Clojure, Groovy, Scala und andere Sprachen gezeigt haben, ist es möglich, (1) eine neue Sprache zu definieren (2) die gesamte Fülle von Bibliotheken und Tools zu nutzen, die bereits in Java geschrieben sind, ohne die Abwärtskompatibilität zu beeinträchtigen (3) Java-Programmierer können frei wählen, ob sie bei Java bleiben, in eine andere Sprache wechseln oder beides verwenden möchten. IMO, eine bestehende Sprache auf unbestimmte Zeit zu erweitern, ist eine Strategie, um Kunden zu halten, so wie Mobilfunkunternehmen ständig neue Modelle erstellen müssen.
Giorgio
2
Wie ich von SAMbdas in Java verstanden habe , ist einer der Gründe, dass es problematisch wäre, einige Bibliotheken (Sammlungen) abwärtskompatibel zu halten.
Petr Pudlák
2
Verwandter Beitrag auf SO, der auf diesen anderen Beitrag von Goetz verweist .
Assylias

Antworten:

6

Der SAM-Ansatz ähnelt der Vorgehensweise von Scala (und C ++ 11) mit anonymen Funktionen (die mit dem =>Operator von Scala oder der []()(Lambda) -Syntax von C ++ 11 erstellt wurden ).

Die erste Frage, die auf der Java-Seite beantwortet werden muss, ist, ob der Rückgabetyp einer Lambda-Anweisung ein neuer primitiver Typ wie intoder byteoder ein Objekttyp irgendeiner Art sein soll. In Scala gibt es keine primitiven Typen - selbst eine Ganzzahl ist ein Objekt der Klasse Int- und Funktionen unterscheiden sich nicht, da sie Objekte der Klasse Function1sind Function2, und so weiter, abhängig von der Anzahl der Argumente, die die Funktion annimmt.

C ++ 11, Ruby und Python haben ebenfalls Lambda-Ausdrücke, die ein Objekt zurückgeben, das explizit oder implizit aufrufbar ist. Das zurückgegebene Objekt verfügt über eine Standardmethode (z. B. eine Methode, mit #callder es als Funktion aufgerufen werden kann. In C ++ 11 wird beispielsweise ein std::functionTyp verwendet, der überladen wird, operator()sodass Aufrufe der Aufrufmethode des Objekts sogar textuell wie Funktionsaufrufe aussehen . :-)

Der neue Vorschlag für Java besteht darin, eine strukturelle Typisierung zu verwenden, um die Zuweisung einer solchen Methode zu einem anderen Objekt zu ermöglichen, beispielsweise zu einem Objekt, Comparatordas eine einzige Hauptmethode mit einem anderen Namen hat . Obwohl dies in gewisser Hinsicht konzeptionell schwierig ist, bedeutet dies, dass das resultierende Objekt an vorhandene Funktionen übergeben werden kann, die ein Objekt zur Darstellung eines Rückrufs, eines Komparators usw. benötigen und erwarten, dass sie eine wohldefinierte Single aufrufen können Methode wie #compareoder #callback. Der Trick von C ++, operator()dieses Problem zu überschreiben, hat dieses Problem bereits vor C ++ 11 sauber umgangen, da alle derartigen Rückrufoptionen auf dieselbe Weise aufgerufen werden konnten und die STL daher keine Anpassung erforderte, um die Verwendung von C ++ 11-Lambdas zuzulassensortund so weiter. Java, das in der Vergangenheit keine Standardbenennung für solche Objekte verwendet hat (vielleicht, weil kein Trick wie das Überladen von Operatoren einen einzigen Ansatz offensichtlich machte), ist nicht so glücklich, und so verhindert dieser Hack, dass sie eine ganze Menge bestehender APIs ändern müssen .

Jimwise
quelle
1
Ironischerweise hätte das Problem, wenn es unzählige verschiedene, inkompatible Typen gegeben hätte, die alle eine Art "funktionsähnliches Ding" repräsentierten, vermieden werden können, indem der Bibliothek frühzeitig Standardfunktionstypen hinzugefügt worden wären, unabhängig von der tatsächlichen wörtlichen Syntax für deren Erstellung. Wenn es java.util.Function2<T1, T2, R>in Java 1.0 eine gegeben hätte, gäbe es keine ComparatorSchnittstelle, stattdessen würden alle diese Methoden eine annehmen Function2<T1, T2, int>. (Nun, es hätte eine ganze Reihe von Schnittstellen gegeben, zum Beispiel Function2TT_int<T1, T2>aufgrund von Primitiven, aber Sie verstehen, worum es geht.)
Jörg W. Mittag,