Montag, 20. September 2010

Scala: Klassen erweitern mal anders

Datentypen erweitern ist mehr oder weniger etwas das man des öfteren machen muss, und das ist nicht umbedingt immer unproblematisch. Nehmen wir mal an wir hätten folgende Klasse:

class Test(val x: Int)

Ganz klar: hochkomplizierter Code. Test quillt an Features nur so über, aber uns fehlt gerade eine Methode damit
wir Objekte dieser Klasse in unserem Code nahtlos verwenden können. Diese Methode nennen wir bla. Das Problem ist allerdings: Wir haben den Code von Test nicht und können ihn deswegen nicht um bla erweitern.
Jetzt gibt es verschiedene Wege wie wir diese Methode in Test einbinden könnten. Der offensichtlichste ist sicherlich Vererbung. Blöd ist nur: Wir haben bla schon geschrieben und würden ihn ungern duplizieren, vor allem weil wir den Code bereits schön generalisiert haben.

Eine Lösung ist hier ziemlich elegant mit Traits, Selftyping und Structural Types zu machen.

Als erstes definieren wir einen Structural Type der eine genaue Schnittstelle definiert. Wir nennen ihn xAble und er macht nichts weiter als zu definieren das alle Klassen die eine Methode x anbieten vom Typ xAble sind.

type xAble = { def x: Int }

Was wir jetzt brauchen ist unsere generalisierte Implementation von bla. Hier verwenden wir, Selftyping. Das ist ein Feature das es uns erlaubt innerhalb eines Traits so zu tun als wäre es von einem bestimmten Typ ohne davon zu erben.

trait Gimme[A <: xAble] {
self: A =>
def bla(): Unit = println(self.x + 1)
}

Wie man sieht muss der muss der Typparameter A ein Subtyp von xAble sein, also die methode x liefern.

Alles was wir jetzt noch machen müssen ist das Trait Gimme in das Objekt des Typs Test zu mischen und wir haben das ganze um bla erweitert.

object Main {

class Test(val x: Int)

type xAble = { def x: Int }

trait Gimme[A <: xAble] {
self: A =>
def bla(): Unit = println(self.x + 1)
}

def main(args: Array[String]): Unit = {
val t = new Test(666) with Gimme[Test]
t.bla()
}
}

Das ganze Zeit mal wieder wie extrem mächtig das Scala Typsystem ist und was für elegante Features die Sprache zum lösen immer wiederkehrender Probleme bietet. Ich hoffe dieses Beispiel gibt euch eine kleine Idee was man damit machen kann. ;)

3 Kommentare:

Anonym hat gesagt…

Hallo xRaich[o]²x,

ich verstehe nicht ganz, warum Du unbedingt eine Selftype Annotation einsetzen musst. Ich denke Du wolltest damit erreichen, 'bequem' auf Member x der Klasse Test innerhalb Deines Traits zuzugreifen, nach dem Motto 'dieses Traits darf nur in Typen hinzugemixt werden, welche letztendlich ein Member x vom Typ Int anbieten.

Dies könntest du allerdings auch ohne selftype Annotation erreichen, indem Du in Deinem Trait eine entsprechende abstrakte Methode einführst:

class Test(val x: Int)

trait GimmeBlah {
def x :Int
def bla(): Unit =
println(x + 1)
}

val t = new Test(777) with GimmeBlah

t.bla()

Auch hier kannst Du letztendlich zur Compile-Zeit nur einen gültigen Mixin-Typ (ohne compile error) erzeugen, wenn die abstrakte Methode eine Implementierung erhält, welche im Falle der Klasse Test dem Konstruktor-Value x entspricht.

Vielleicht übersehe ich hier etwas bzgl. Deiner Problem-Konstellation?

Viele Grüsse

Mario

raichoo hat gesagt…

Hi,

Danke für deinen Kommentar. Das ganze Beispiel ist rein akademisch und sollte gar kein Problem lösen. Es handelt sich dabei um nichts mehr als eine Spielerei zum eigenen Verständnis :)

Gruß
raichoo

Anonym hat gesagt…

okido,

auf jeden Fall hat es 'structural types' und 'selftype annotations' in interessanten Zusammenhang gebracht :o)

Grüsse

Mario