Warum kann der Scala-Compiler keine Mustervergleichswarnung für nicht versiegelte Klassen / Merkmale geben?

10

Ich frage mich, ob der Compiler zur Kompilierungszeit für diesen bestimmten Patternmatch nicht weiß, welche möglichen Implementierungen dieses Merkmals / dieser Klasse verfügbar sind, wenn ich ein nicht versiegeltes traitoder abstract classin Scala verwendetes Muster verwende und dann den Mustervergleich verwende. Wenn dies der Fall ist, könnte es dann keine Warnungen vor Musterübereinstimmungen geben, obwohl das trait/ abstract classnicht versiegelt ist, weil er weiß, welche Typen verwendet werden könnten , indem alle möglichen Abhängigkeiten / Importe überprüft werden?

Wenn ich zB ein habe Option[A]und nur einen Mustervergleich für, Some[A]aber nicht für mache, Nonebeschwert sich der Compiler, weil er Optionversiegelt ist.

Wenn der Compiler das nicht wissen / lösen kann, warum kann er es dann nicht? Und wenn der Compiler dies (theoretisch) kann, was sind die Gründe dafür, dass er in Scala nicht verwendet wird? Gibt es andere Sprachen, die dieses Verhalten unterstützen?

Valenterry
quelle
Es ist unklar, was Sie fragen. Möchten Sie, dass der Compiler eine Warnung ausgibt, wenn der Übereinstimmungsausdruck nicht alle möglichen Eingaben abdeckt? Vielleicht würde ein Beispiel Ihre Frage klarer machen.
kdgregory
2
Wenn jemand eine neue Unterklasse einführen kann, kann die Musterübereinstimmung niemals erschöpfend sein. Beispiel: Sie erstellen eine abstrakte Klasse Foomit Unterklassen A, Bund C, und alle Ihre Musterübereinstimmungen stimmen nur mit diesen drei überein. Nichts hindert mich daran, eine neue Unterklasse hinzuzufügen D, die Ihre Musterübereinstimmungen in die Luft jagt.
Doval
@kdgregory Ja, du hast es verstanden. Ich habe ein Beispiel hinzugefügt, um es klarer zu machen.
Valenterry
3
Das Überprüfen aller Importe reicht nicht aus, um alle möglichen Unterklassen zu ermitteln. Eine andere Unterklasse kann in einer separaten Klassendatei deklariert werden, die später zur Laufzeit über geladen wird java.lang.ClassLoader.
Amon

Antworten:

17

Das Herausfinden aller Unterklassen einer Klasse wird als Klassenhierarchieanalyse bezeichnet. Das Ausführen einer statischen CHA in einer Sprache mit dynamischem Code-Laden entspricht der Lösung des Halteproblems.

Außerdem ist eines der Ziele von Scala die separate Kompilierung und Bereitstellung unabhängiger Module, sodass der Compiler einfach nicht wissen kann, ob eine Klasse in einem anderen Modul untergeordnet ist oder nicht, da nie mehr als ein Modul betrachtet wird. (Schließlich können Sie ein Modul gegen die Schnittstelle eines anderen Moduls kompilieren, ohne dass dieses Modul überhaupt auf Ihrem System vorhanden ist!) Aus diesem Grund sealedmüssen alle Unterklassen in derselben Kompilierungseinheit definiert sein.

Dies ist auch einer der Gründe, warum JVMs so günstig mit C ++ - Compilern konkurrieren können: C ++ - Compiler sind in der Regel statische Compiler, sodass sie im Allgemeinen nicht herausfinden können, ob eine Methode überschrieben wird oder nicht, und sie daher nicht einbinden können. JVMs OTOH sind normalerweise dynamische Compiler. Sie müssen keine CHA ausführen, um herauszufinden, ob eine Methode überschrieben wird oder nicht. Sie können sich zur Laufzeit nur die Klassenhierarchie ansehen. Und selbst wenn zu einem späteren Zeitpunkt in der Ausführung des Programms eine neue Unterklasse hinzukommt, die vorher nicht vorhanden war, keine große Sache, kompilieren Sie einfach diesen Code ohne Inlining neu.

Hinweis: All dies gilt nur innerhalb von Scala. Die JVM hat keine Ahnung davon sealed, daher ist es durchaus möglich, sealedKlassen aus einer anderen JVM-Sprache zu unterordnen, da es keine Möglichkeit gibt, dies einer anderen Sprache mitzuteilen. Die sealedEigenschaft wird in der ScalaSigAnnotation aufgezeichnet , aber die Compiler anderer Sprachen berücksichtigen diese Annotationen offensichtlich nicht.

Jörg W Mittag
quelle
3

Es kann durchgeführt werden (zumindest für alle Klassen, die zur Kompilierungszeit bekannt sind), es ist nur teuer. Sie würden die inkrementelle Kompilierung vollständig zerstören, da alles, was eine Musterübereinstimmung enthält, bei jeder Änderung einer anderen Datei neu kompiliert werden müsste.

Und was kaufst du? Es ist ein Codegeruch, Musterübereinstimmungen zu schreiben, die sich häufig ändern müssen, wenn eine neue abgeleitete Klasse hinzugefügt wird. Es ist eine Verletzung des Open / Closed-Prinzips . Verwenden Sie die Vererbung richtig und Sie müssen diese Art von Musterübereinstimmungen nicht schreiben. Und ja, das Open / Closed-Prinzip gilt auch für funktionale Sprachen ohne klassenbasierte Vererbung. Tatsächlich erleichtern funktionale Sprachen zwischen Funktionen wie Typklassen, Multimethoden und einfachen Funktionen höherer Ordnung die Erweiterung ohne Änderung erheblich.

Karl Bielefeldt
quelle
1
It can be done (at least for all classes known at compile time), it's just expensive.Aber wenn das Programm nicht zu 100% in sich geschlossen ist (dh von externen .jarDateien abhängt ), können Sie sich nach dem Kompilieren durch eines der jars nicht in eine neue Unterklasse einschleichen ? Der Compiler könnte Ihnen also sagen, dass "Ihre Musterübereinstimmungen jetzt vollständig sind, aber das kann sich ändern, wenn sich eine Ihrer Abhängigkeiten ändert", was ziemlich wertlos ist, da der Sinn externer Abhängigkeiten darin besteht, sie ohne erneutes Kompilieren aktualisieren zu können!
Doval
Daher der Haftungsausschluss. In der Praxis möchten Sie ohnehin neu kompilieren, wenn Sie eng genug miteinander verbunden sind, um eine umfassende Übereinstimmung mit einer externen oder sonstigen Abhängigkeit zu benötigen.
Karl Bielefeldt
@Doval Dann sollten Sie eine Form der Delegierung verwenden und die aufgerufene Klasse entscheiden lassen, was zu tun ist, und die Kontrolle umkehren. Dafür war OOP gedacht. Wenn Sie das nicht wollen, haben Sie Ihr aktuelles Problem.
Schlitten
@ArtB Ich sehe nicht, was Delegation oder Umkehrung der Kontrolle damit zu tun haben.
Doval
Wenn Sie möchten, dass es mit Personen funktioniert, die es extern hinzufügen können, rufen Sie die Klasse auf und verschieben Sie die Logik in diese Klassen.
Schlitten