Wie kann ich in Scala den Namen der Felder einer Fallklasse programmgesteuert ermitteln?

8

In Scala nehme ich an, dass ich eine Fall-Klasse wie folgt habe:

%Vor%

Gibt es eine Möglichkeit für mich, eine Seq[(String, Class[_])] oder besser noch Seq[(String, Manifest)] zu erhalten, die die Parameter der Fallklasse beschreibt?

    
Jean-Philippe Pellet 08.06.2011, 17:00
quelle

3 Antworten

1

Ich bin es wieder (zwei Jahre später). Hier ist eine andere, andere Lösung mit Scala Reflexion. Es ist inspiriert von einem Blogpost , die selbst von einem Stack Overflow Austausch inspiriert wurde. Die folgende Lösung ist auf die obige Frage des ursprünglichen Posters spezialisiert.

Fügen Sie in einer Kompilierungseinheit (eine REPL :paste oder eine kompilierte JAR) scala-reflect als Abhängigkeit ein und kompilieren Sie Folgendes (getestet in Scala 2.11, könnte in Scala 2.10 funktionieren):

%Vor%

Und in einer anderen Kompilierungseinheit (die nächste Zeile in der REPL oder Code mit dem vorherigen als eine Abhängigkeit kompiliert), verwenden Sie es wie folgt:

%Vor%

Es scheint übertrieben zu sein, aber ich konnte es nicht kürzer kriegen. Hier ist was es tut:

  1. Die Funktion caseClassFields erstellt ein intermediate CaseClassFieldsExtractor , das implizit entsteht, seine Ergebnisse meldet und verschwindet.
  2. CaseClassFieldsExtractor ist ein Merkmal mit einem Begleitobjekt, das eine anonyme konkrete Unterklasse dieses Merkmals mithilfe eines Makros definiert. Es ist das Makro, das die Felder Ihrer Fallklasse überprüfen kann, da es umfangreiche Informationen auf Compiler-Ebene über die Fallklasse enthält.
  3. Das CaseClassFieldsExtractor und sein Companion-Objekt müssen in einer vorherigen Kompilierungseinheit deklariert sein, die die Fallklasse überprüft, damit das Makro zu dem Zeitpunkt existiert, an dem Sie es verwenden möchten.
  4. Die Typdaten Ihrer Fallklasse werden über WeakTypeTag übergeben. Dies ergibt eine Scala-Struktur mit vielen Musterabgleich und keine Dokumentation, die ich finden konnte.
  5. Wir nehmen wieder an, dass es nur einen ("primären"?) Konstruktor gibt, aber ich denke, dass alle in Scala definierten Klassen nur einen Konstruktor haben können. Da diese Technik die Felder des Konstruktors und nicht alle JVM-Felder in der Klasse untersucht, ist es nicht anfällig für die fehlende Allgemeinheit, die meine vorherige Lösung beeinträchtigt hat.
  6. Es verwendet Quasiquotes, um eine anonyme, konkrete Unterklasse von CaseClassFieldsExtractor .
  7. aufzubauen
  8. Bei diesem "impliziten" Geschäft kann das Makro in einem Funktionsaufruf ( caseClassFields ) definiert und eingegrenzt werden, ohne dass es zu früh aufgerufen wird, wenn es noch nicht definiert ist.

Alle Kommentare, die diese Lösung verbessern oder erklären könnten, wie genau die "implicits" tun, was sie tun (oder ob sie entfernt werden können), sind willkommen.

    
Jim Pivarski 10.02.2016, 02:00
quelle
7

Ich beantworte meine eigene Frage, um eine Basislösung bereitzustellen, aber ich suche auch nach Alternativen und Verbesserungen.

Eine Option, die auch mit Java kompatibel und nicht auf Fallklassen beschränkt ist, ist ParaNamer . In Scala besteht eine weitere Möglichkeit darin, die ScalaSig Bytes, die an generierte Klassendateien angehängt sind, zu analysieren. Beide Lösungen funktionieren nicht in der REPL.

Hier ist mein Versuch, die Namen der Felder aus ScalaSig (die Scalap und Scala 2.8.1 verwendet) zu extrahieren:

%Vor%

Haftungsausschluss: Ich verstehe die Struktur von ScalaSig nicht wirklich und dies sollte als Heuristik betrachtet werden. Dieser Code trifft insbesondere die folgenden Annahmen:

  • Fallklassen haben nur einen Konstruktor.
  • Die Eingabe der Signatur an der Position Null ist immer ein ClassSymbol .
  • Der relevante Konstruktor der Klasse ist der erste MethodEntry mit dem Namen <init> , dessen Besitzer die ID 0 hat.
  • Die Parameternamen haben als Besitzer den Konstruktoreintrag und immer nach diesem Eintrag.

Es wird fehlschlagen (wegen ScalaSig ) bei verschachtelten Fallklassen.

Diese Methode gibt auch nur Class Instanzen und nicht Manifest s zurück.

Bitte zögern Sie nicht, Verbesserungen vorzuschlagen!

    
Jean-Philippe Pellet 08.06.2011 17:02
quelle
3

Hier ist eine andere Lösung, die Plain-Java-Reflektion verwendet.

%Vor%

Um eine Seq[(String, Class[_])] zu erhalten, können Sie dies tun:

%Vor%

Ich bin mir nicht sicher, wie ich Manifests bekommen kann.

    
Jim Pivarski 26.08.2013 05:33
quelle

Tags und Links