Scala Schritt für Schritt

Scala Tutorial Teil 5: Pfadabhängige Typen, Self Types und Typklassenvariablen

Johannes Dienst

© Shutterstock.com / Aquir

Als Multiparadigmensprache besitzt Scala neben Objektorientierung auch ausgeprägte Merkmale einer funktionalen Sprache. Dadurch wird sie für viele Einsatzgebiete interessant. In diesem Tutorial führen wir Schritt für Schritt in die Programmierung mit Scala ein. Viel Spaß!

.
.
.

Scala Tutorial - Pfadabhängige Typen

Die Vorteile einer statisch typisierten Sprache liegen in der Erkennung von Fehlern zur Kompilierzeit. Der Compiler von Scala ist intelligent und leistungsfähig, was man bereits im letzten Teil dieses Tutorials im Bezug auf Type Classes erkennen konnte.

Pfadabhängige Typen sind ein weiteres Mittel, um Laufzeitfehler gezielt zu vermeiden. Im Gegensatz zu Type Classes wird man im normalen Entwickleralltag jedoch weniger damit konfrontiert. Ein grundsätzliches Verständnis dafür ist aber nötig, damit diese Technik gezielt eingesetzt werden kann. Auch, um sich unter Umständen bei zufälliger Verwendung einigen Frust zu ersparen.

Wo liegt denn jetzt genau der Anwendungsfall von pfadabhängigen Typen? Eigentlich jedes Mal, wenn der Typ eines Objekts von einem anderen Objekt abhängt. Normalerweise ist eine solche Typauflösung erst zur Laufzeit möglich, was zu ungültigen Zuweisungen führen kann. In Scala kann schon zur Kompilierzeit genau das ausgeschlossen werden.

Ein weiteres Mittel zur Sicherung der Typsicherheit sind Typklassenvariablen, die ähnliche Funktionen wie Typparameter (Generics) besitzen. Um diese Konzepte genauer zu beleuchten, wollen wir uns zuerst Self Types anschauen, mit denen es unter anderem möglich ist, Traits/Klassen explizit voneinander abhängig zu machen. Die Unterschiede von Typparametern zu Typklassenvariablen werden anschließend anhand eines Vergleichs der beiden Möglichkeiten deutlich gemacht.

Pfadabhängige Typen

Um pfadabhängige Typen verstehen zu können, hilft ein konkretes Beispiel, an dem die Funktionsweise erläutert wird. Es werden dazu spezialisierte Versionen von Bibliotheken erstellt. Zuerst eine Version LibraryDRM, die nur mit Kopierschutz ausgestattete Bücher verleiht (Listing 1). Sie besitzt neben einer Minimalausstattung mit einer Klassenvariable books und einer Methode zum Hinzufügen von Büchern auch eine innere Klasse DRMBook, die für dieses Beispiel lediglich die Klasse Book erweitert.

class LibraryDRM extends Library {

val books = ListBuffer[Book]()

def add(book: Book) { books += book }

class DRMBook(
title: String,
val author: String,
isbn10: Long = -1)
extends Book(title, author, isbn10) { }
}

Als zweites erzeugen wir eine Klasse LibraryOnline, die nur online verfügbare Bücher verleiht (Listing 2). Auch diese ist wie LibraryDRM minimal aufgebaut mit einer inneren Klasse OnlineBook, die ebenfalls Book erweitert.

class LibraryOnline extends Library {

  val books = ListBuffer[Book]()

  def add(book: Book) { books += book }

  class OnlineBook(
    title: String,
    val author: String,
    isbn10: Long = -1)
    extends Book(title, author, isbn10) { }
}

Interessant wird es, wenn mit den Klassen ein wenig herumgespielt wird. Dazu werden zuerst einmal Objekte vom Typ der jeweiligen Libraries erzeugt und entsprechende Objekte vom Typ der inneren Klassen DRMBook und OnlineBook:

val libraryOnline = new LibraryOnline()
var onlineBook = new libraryOnline.OnlineBook("The neophytes guide to Scala", "Kaffeecoder", -1)
var onlineBook2 = new libraryOnline.OnlineBook("Clean Code", "Uncle Bob", 3826655486L)

val libraryOnline2 = new LibraryOnline()
var onlineBook3 = new libraryOnline2.OnlineBook("The Pragmatic Programmer", "Andrew Hunt", 3446223096L)

val libraryDRM = new LibraryDRM()
var drmBook = new libraryDRM.DRMBook("Stufen", "Hermann Hesse", 3458357475L)

Soweit so gut, könnte man sich denken. Bis jetzt verhält sich alles so, wie es zu erwarten ist, was man leicht überprüfen kann:

onlineBook = drmBook // Type mismatch
onlineBook = onlineBook2

Spannend wird es, wenn ein Objekt vom Typ LibraryOnline.OnlineBook einem anderen Objekt vom Typ LibraryOnline.OnlineBook zugewiesen werden soll. Wenn das Letztere aus einem anderen Objekt vom Typ LibraryOnline erstellt worden ist, dann ist diese Zuweisung nicht gültig:

    /*
     * Produces:
     *   type mismatch; found : libraryOnline2.OnlineBook
     *   required: libraryOnline.OnlineBook
     */
    onlineBook = onlineBook3

Hier gibt es unerwarteterweise einen Typfehler. Und zwar bemängelt der Compiler, dass die Instanzen vom Typ LibraryOnline.OnlineBook nicht aus dem selben Objekt erzeugt wurden. Er ist also in der Lage, zur Kompilierzeit festzustellen, was in anderen Sprachen erst zur Laufzeit bekannt ist. Die Bezeichnung pfadabhängige Typen ist meiner Meinung nach nicht so deutlich. Tatsächlich handelt es sich eher um wertabhängige/objektabhängige Typen, da der endgültige Typ eines Objekts LibraryOnline.OnlineBook vom Mutterobjekt, aus dem es erstellt worden ist, abhängt. Pfadbhängige Typen sind nicht das tägliche Brot eines Scala-Entwicklers. Trotzdem ist es wichtig, wenigstens schon einmal davon gehört zu haben, um Frustration im Zusammenhang mit inneren Klassen zu vermeiden.

Verwandte Themen:

Geschrieben von
Johannes Dienst
Johannes Dienst
Johannes Dienst ist Clean Coder aus Leidenschaft bei der MULTA MEDIO Informationssysteme AG. Seine Tätigkeitsschwerpunkte sind die Wartung und Gestaltung von serverseitigen Java, Python und JavaScript-Applikationen. Twitter: @JohannesDienst
Kommentare
  1. Heiner Kücker2017-09-06 09:39:33

    Im Abschnitt 'Pfadabhängige Typen' finde ich es schade, dass bei der Variable onlineBook Typ-Inferenz benutzt wird.
    Dadurch sieht man nicht, wie ein pfadabhängiger Typ notiert wird.
    Bei der Typ-Inferenz bekommt die Variable onlineBook scheinbar den am meisten restriktiven Typen, also mit Pfad, bei Angabe eines schwächeren Typen

    var onlineBook : OnlineBook = ...

    oder so, wäre wahrscheinlich die Zuweisung

    onlineBook = onlineBook3

    erlaubt.

Schreibe einen Kommentar

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