Hilfe zur Selbsthilfe: Selbst ist der Typ!


Heute möchte ich einen Trick ein Idiom vorstellen, den ich in Matt Malone’s Old-Fashioned Software Development Blog gefunden habe.

Angenommen, wir wollen Tiere und ihre Verwandschaftsverhältnisse abbilden, etwa so:

trait Animal {
  val name:String
  val father:Option[Animal]
  val mother:Option[Animal]
}

case class Cat(name:String, father:Option[Animal], mother:Option[Animal]) 
   extends Animal

case class Dog(name:String, father:Option[Animal], mother:Option[Animal]) 
   extends Animal

Ich habe für die Eltern den Typ Option gewählt, weil diese ja eventuell nicht bekannt sind. So weit so gut. Probieren wir das Ganze einmal aus:

val tom = Cat("Tom", None, None)
val susi = Cat("Susi", None, None)
val tiger = Dog("Tiger", Some(tom), Some(susi))

Ahem, da ist wohl etwas schief gelaufen: Falls Tiger nicht adoptiert worden ist, sind hier die Artgrenzen überschritten worden. Wir müssen also dafür sorgen, dass die Eltern den gleichen Typ wie das Kind hat. In Java macht uns die Klasse Enum eine mögliche Lösung vor:

 
//Java 1.5+
interface Animal<A extends Animal<A>> {
  String getName();
  A getFather();
  A getMother();
}

Das sieht zwar verwirrend aus, funktionert aber in Java wie in Scala.

Der erste Schritt von Matt Malone’s Variante ähnelt der Java-Lösung:

trait Animal[T] {
  val name:String
  val father:Option[T]
  val mother:Option[T]
}

case class Cat(name:String, father:Option[Cat], mother:Option[Cat]) 
    extends Animal[Cat]

val tom = Cat("Tom", None, None)
val susi = Cat("Susi", None, None)
val tiger = Cat("Tiger", Some(tom), Some(susi))

Allerdings könnte ein böswilliger Programmierer auf die Idee kommen zu schummeln:

case class Dog(name:String, father:Option[Cat], mother:Option[Cat]) 
   extends Animal[Cat]

val tiger = Dog("Tiger", Some(tom), Some(susi))

Während es in Java nur den oben gezeigten Trick mit der rekursiven Typdefinition gibt, hat Scala ein weiteres As im Ärmel: Selbst-Typen (offiziell explizit getypte Selbstreferenzen genannt). Damit können wir in unserem Fall den Typ T auf den eigenen Typ einschränken:

trait Animal[T] {
  self:T =>
  val name:String
  val father:Option[T]
  val mother:Option[T]
}

case class Cat(name:String, father:Option[Cat], mother:Option[Cat]) 
  extends Animal[Cat]

case class Dog(name:String, father:Option[Dog], mother:Option[Dog]) 
  extends Animal[Dog]

Jetzt sind keine Mogeleien mehr möglich, die Hundeklasse aus dem vorherigen Beispiel würde nicht mehr compilieren. Und übersichtlicher als die Java-Variante ist es allemal.

Natürlich ist das nur eine von vielen Einsatzmöglichkeiten von Selbst-Typen. Andere Anwendungsfälle habe ich hier z.B. in den Beiträgen „Abstrakte Kunst“ und „Auf die Bäume, ihr Affen!“ vorgestellt.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s