Typ Löschung ist gut
Bleiben wir bei den Fakten
Viele der bisherigen Antworten betreffen den Twitter-Nutzer übermäßig. Es ist hilfreich, sich auf die Nachrichten und nicht auf den Messenger zu konzentrieren. Selbst mit den bisher genannten Auszügen gibt es eine ziemlich konsistente Botschaft:
Es ist lustig, wenn sich Java-Benutzer über das Löschen von Typen beschweren, was das einzige ist, was Java richtig gemacht hat, während sie alle Dinge ignorieren, die falsch gemacht wurden.
Ich bekomme enorme Vorteile (z. B. Parametrizität) und keine Kosten (angebliche Kosten sind eine Grenze der Vorstellungskraft).
neues T ist ein kaputtes Programm. Es ist isomorph zu der Behauptung "Alle Aussagen sind wahr". Ich bin nicht groß darin.
Ein Ziel: vernünftige Programme
Diese Tweets spiegeln eine Perspektive wider, die nicht daran interessiert ist, ob wir die Maschine dazu bringen können, etwas zu tun , sondern vielmehr, ob wir begründen können, dass die Maschine etwas tut, was wir tatsächlich wollen. Gute Argumentation ist ein Beweis. Beweise können in formaler Notation oder etwas weniger Formalem angegeben werden. Unabhängig von der Spezifikationssprache müssen sie klar und streng sein. Informelle Spezifikationen sind nicht unmöglich korrekt zu strukturieren, weisen jedoch häufig Fehler in der praktischen Programmierung auf. Wir haben am Ende Abhilfemaßnahmen wie automatisierte und explorative Tests, um die Probleme auszugleichen, die wir mit informellen Überlegungen haben. Dies bedeutet nicht, dass das Testen an sich eine schlechte Idee ist, aber der zitierte Twitter-Benutzer schlägt vor, dass es einen viel besseren Weg gibt.
Unser Ziel ist es daher, korrekte Programme zu haben, über die wir klar und konsequent in einer Weise nachdenken können, die der tatsächlichen Ausführung des Programms durch die Maschine entspricht. Dies ist jedoch nicht das einzige Ziel. Wir wollen auch, dass unsere Logik ein gewisses Maß an Ausdruckskraft hat. Zum Beispiel gibt es nur so viel, was wir mit Aussagenlogik ausdrücken können. Es ist schön, eine universelle (∀) und existenzielle (∃) Quantifizierung aus so etwas wie Logik erster Ordnung zu haben.
Verwenden von Typensystemen zum Denken
Diese Ziele können von Typsystemen sehr gut angesprochen werden. Dies ist besonders deutlich aufgrund der Curry-Howard-Korrespondenz . Diese Entsprechung wird oft mit folgender Analogie ausgedrückt: Typen beziehen sich auf Programme wie Theoreme auf Beweise.
Diese Entsprechung ist etwas tiefgreifend. Wir können logische Ausdrücke nehmen und sie durch die Entsprechung zu Typen übersetzen. Wenn wir dann ein Programm mit derselben Typensignatur haben, die kompiliert wird, haben wir bewiesen, dass der logische Ausdruck universell wahr ist (eine Tautologie). Dies liegt daran, dass die Korrespondenz in beide Richtungen erfolgt. Die Transformation zwischen der Typ- / Programm- und der Theorem- / Beweiswelt ist mechanisch und kann in vielen Fällen automatisiert werden.
Curry-Howard spielt gut mit dem, was wir mit Spezifikationen für ein Programm machen möchten.
Sind Typsysteme in Java nützlich?
Selbst mit einem Verständnis von Curry-Howard fällt es manchen Menschen leicht, den Wert eines Typsystems zu verwerfen, wenn dies der Fall ist
- ist extrem schwer zu bearbeiten
- entspricht (durch Curry-Howard) einer Logik mit begrenzter Ausdruckskraft
- ist kaputt (was zur Charakterisierung von Systemen als "schwach" oder "stark" führt).
In Bezug auf den ersten Punkt machen IDEs das Typensystem von Java möglicherweise einfach genug, um damit zu arbeiten (das ist sehr subjektiv).
In Bezug auf den zweiten Punkt entspricht Java fast einer Logik erster Ordnung. Generika verwenden das Typensystem, das der universellen Quantifizierung entspricht. Leider geben Wildcards nur einen kleinen Teil der existenziellen Quantifizierung. Aber die universelle Quantifizierung ist ein ziemlich guter Anfang. Es ist schön sagen zu können, dass Funktionen List<A>
für alle möglichen Listen universell funktionieren, da A völlig frei ist. Dies führt zu dem, worüber der Twitter-Nutzer in Bezug auf "Parametrizität" spricht.
Ein oft zitiertes Papier über Parametrizität sind Philip Wadlers Theoreme kostenlos! . Das Interessante an diesem Artikel ist, dass wir allein anhand der Typensignatur einige sehr interessante Invarianten nachweisen können. Wenn wir automatisierte Tests für diese Invarianten schreiben würden, würden wir unsere Zeit sehr verschwenden. Zum Beispiel für List<A>
, von der Typensignatur allein fürflatten
<A> List<A> flatten(List<List<A>> nestedLists);
das können wir begründen
flatten(nestedList.map(l -> l.map(any_function)))
≡ flatten(nestList).map(any_function)
Das ist ein einfaches Beispiel, und Sie können wahrscheinlich informell darüber nachdenken, aber es ist noch schöner, wenn wir solche Beweise formell kostenlos vom Typsystem erhalten und vom Compiler überprüft werden.
Nicht löschen kann zu Missbrauch führen
Aus der Perspektive der Sprachimplementierung spielen Javas Generika (die universellen Typen entsprechen) sehr stark mit der Parametrizität, die verwendet wird, um Beweise für die Funktionsweise unserer Programme zu erhalten. Dies führt zu dem dritten erwähnten Problem. All diese Beweise und Korrektheiten erfordern ein fehlerfreies Soundsystem. Java hat definitiv einige Sprachfunktionen, die es uns ermöglichen, unsere Argumentation zu zerstören. Dazu gehören unter anderem:
- Nebenwirkungen mit einem externen System
- Reflexion
Nicht gelöschte Generika hängen in vielerlei Hinsicht mit Reflexion zusammen. Ohne Löschung gibt es Laufzeitinformationen, die in der Implementierung enthalten sind, mit der wir unsere Algorithmen entwerfen können. Dies bedeutet, dass wir statisch gesehen nicht das vollständige Bild haben, wenn wir über Programme nachdenken. Reflexion gefährdet ernsthaft die Richtigkeit von Beweisen, über die wir statisch argumentieren. Es ist kein Zufall, dass Reflexion auch zu einer Vielzahl kniffliger Mängel führt.
Wie können nicht gelöschte Generika "nützlich" sein? Betrachten wir die im Tweet erwähnte Verwendung:
<T> T broken { return new T(); }
Was passiert, wenn T keinen Konstruktor ohne Argumente hat? In einigen Sprachen ist das, was Sie erhalten, null. Oder Sie überspringen den Nullwert und lösen direkt eine Ausnahme aus (zu der Nullwerte ohnehin zu führen scheinen). Da unsere Sprache Turing vollständig ist, ist es unmöglich zu überlegen, welche Aufrufe broken
"sichere" Typen mit Konstruktoren ohne Argumente beinhalten und welche nicht. Wir haben die Gewissheit verloren, dass unser Programm universell funktioniert.
Löschen bedeutet, dass wir überlegt haben (also löschen wir)
Wenn wir also über unsere Programme nachdenken möchten, wird dringend empfohlen, keine Sprachfunktionen zu verwenden, die unsere Argumentation stark gefährden. Wenn wir das getan haben, warum nicht einfach die Typen zur Laufzeit löschen? Sie werden nicht benötigt. Wir können eine gewisse Effizienz und Einfachheit erzielen, wenn wir zufrieden sind, dass keine Casts fehlschlagen oder dass beim Aufrufen möglicherweise Methoden fehlen.
Das Löschen fördert das Denken.