Neues in Scala 2.9 - JAXenter

Neues in Scala 2.9

Dynamic

Eine weitere Neuerung von Scala 2.9 besteht in der eingangs erwähnten Möglichkeit, dynamische Methodenaufrufe zu programmieren.

Betrachten wir das am Beispiel von Datenbankzugriffscode. In vielen Anwendungen möchte man Entitäten anhand des Werts eines Attributs finden, z. B. Personen anhand ihres Vor- oder Nachnamens. Die Implementierungen der entsprechenden Finder-Methoden sind im Wesentlichen immer gleich, und es wäre schön, wenn man sie in eine allgemeine Klasse auslagern könnte (Listing 4).

Listing 4: Aufrufe dynamischer Methoden
object DynDemo {
    def main (args: Array[String]) {
        val f = new GenericFinder ()
        
        println (f.findByFirstName ("Arno"))
        println (f.findByLastName  ("Haase"))
    }
}

Dieses Objekt ruft die Methoden findByFirstName und findByLastName auf, und die Namen der Methoden sollen festlegen, nach welchem Attribut in der Datenbank gesucht werden soll. Diese Aufrufseite ist dabei noch nicht überraschend.

Spannend wird die Implementierung von GenericFinder – wir wollen ja nur eine einzige, allgemeine Implementierung haben, bei der alle find-Aufrufe landen. Der Schlüssel liegt darin, vom neuen Trait Dynamic zu erben (Listing 5).

Listing 5: Eine Klasse mit Dynamic
class GenericFinder extends Dynamic {
  def applyDynamic (name: String) (args: Any*) = 
    if (name.startsWith ("findBy") && args.length == 1) 
      find (name.substring (6), args(0))
  def find (attribName: String, value: Any) = 
    List ("found: " + attribName + " = " + value)
}

Dieser Trait ist ein reiner Marker-Trait, er definiert keine eigenen Methoden. Er sorgt aber dafür, dass Aufrufe, für die eine Klasse keine Implementierungen hat, bei der Methode applyDynamic landen.

Diese Methode muss zusätzlich zu den eigentlichen Methodenparametern eine vorangestellte Parameterliste haben, in der der aufgerufene Methodenname als String übergeben wird. Sie ist im Übrigen eine ganz normale Scala-Methode, die man wie jeden anderen Code implementieren und auch testen kann.

Eine vollständig(er)e Implementierung eines solchen allgemeinen Finders steht in Listing 6. Hier wird der Property-Name entsprechend der Java-Property-Konventionen extrahiert und daraus eine Hibernate Query erzeugt und ausgeführt.

Listing 6: Ein generischer Hibernate Finder auf Basis von Dynamic
import org.hibernate._

abstract class HibernateFinder[T] (session: Session)
                                  (implicit manifest: ClassManifest[T]) 
                                  extends Dynamic {
  val clsName = manifest.erasure.getSimpleName
  
  def applyDynamic (mtdName: String)(arg: Any) = 
      if (mtdName startsWith ("findBy")) {
    def toFirstLower (s: String) = s.head.toLowerCase + s.tail 
    val propName = toFirstLower (mtdName.substring (6))

    val query=session.createQuery("from "+clsName+" where "+propName+"=?")
    query.setParameter (0, arg);
    query.list
  }
  else throw new UnsupportedOperationException(mtdName)
}

Dazu benötigt man den Klassennamen der Entity. Er wird aus dem Typparameter der Klasse ermittelt, was zwar nichts mit dem Dynamic-Trait zu tun hat, aber trotzdem interessant ist.

Zunächst einmal erhält der Konstruktor eine zweite Parameterliste mit einem ClassManifest als implicit-Parameter. Den muss man beim Erzeugen der Klasse nicht explizit angeben, Scala füllt ihn automatisch auf Basis des Typparameters. Diese ClassManifest-Instanz kann man dann (neben anderen Details) nach ihrer erasure fragen, was einfach die Java-Class ist, die dem generischen Parameter entspricht.

Bevor Sie versuchen, die Beispiele auszuführen oder überhaupt dieses neue Feature auszuprobieren: Es ist – zumindest im RC 1, der beim Schreiben des Artikels aktuell war – per Default noch nicht aktiviert. Man muss dem Scala Compiler den Schalter -Xexperimental mitgeben, damit es freigeschaltet wird. In der Eclipse-IDE geht das durch Anwählen der entsprechenden Checkbox unter WINDOW | PREFERENCES | SCALA | COMPILER im Tab-Reiter ADVANCED.

Unter der Oberfläche behandelt der Compiler die Klasse GenericFinder übrigens ohne jede Besonderheit. Nur auf der aufrufenden Seite erzeugt er statt der Aufrufe von findXYZ die entsprechenden Aufrufe von applyDynamic. Damit passt die Umsetzung dieser „dynamischen“ Aufrufe gut in das statische Typsystem von Scala:

  • Wenn eine Klasse eine Methode hat, die schon für einen Aufruf passt, wird diese Methode direkt und ohne den Umweg über applyDynamic aufgerufen.
  • Es steht zur Compile-Zeit fest, welche Methode aufgerufen wird.
  • Es gibt zur Laufzeit keinen Performance-Overhead und keine zusätzlichen Anforderungen an die Laufzeitumgebung (Classloader etc.).
  • Das Feature ist nur für Klassen aktiv, die von Dynamic erben. In allen anderen Fällen bleibt die statische Typprüfung unbeeinflusst.

Außerdem kann man mit Dynamic Aufrufe in dynamische JVM-basierte Sprachen wie JRuby so kapseln, dass deren dynamischer Dispatch von Scala aus zur Verfügung steht. (Das war übrigens die eigentliche Triebfeder für Dynamic in Scala.)

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.