Von Möglichkeiten und Kisten

Die Krux mit der Mehrdeutigkeit von null

Beim Entwickeln stolperte ich neulich (nicht zum ersten Mal) über folgende Situation: Aus einer Map wurden Daten gelesen; die Einträge waren Instanzen einer Klasse, mitunter mußte aber ausgedrückt werden, daß es keine Zuordnung gab – es machte in diesem Fall tatsächlich einen Unterschied, ob es keinen Eintrag in der Map gab, oder ob der Verweis auf „kein Wert“ ging. Gerne verwendet man in Java für „kein Wert“ eben null, was aber gerade im Fall einer Map zu folgender Fehlerquelle führt:

Map<String,MyData> map = ...
if (map.get("blah")!=null) { ... }

Der Code wirft die beiden Fälle zusammen; will man sie unterscheiden, muß man zunächst mit einem map.containsKey(..) prüfen, ob die Map einen entsprechenden Eintrag besitzt. Konsequenz ist, daß unter der Haube zwei Mal auf die Hashtabelle zugegriffen werden muß.

Scala bietet mit der Typklasse Option die Möglichkeit, für jeden beliebigen bestehenden Typ einen solchen „Nullwert“ zu modellieren (das analoge Konzept findet man meines Wissens in Haskell mit dem Datentyp Maybe). Werte vom Typ Option[MyData] sind entweder Some[MyData] wenn sie einen tatsächlichen Wert repräsentieren, oder eben None. Im obigen Beispiel hätte man sich also damit behelfen können, in der Map nicht Werte vom Typ MyData, sondern vom Typ Option[MyData] abzulegen.

Zugegebenermaßen ist das ein eher pathologischer Einsatzzweck; viel häufiger wird Option zur Vermeidung von null-Werten verwendet – weshalb ein Some(null) leichtsam als Trollerei aufgefaßt wird :-)

(Quelle des Comics) Letztendlich braucht man also auch hier eine Konvention, ob ein Wert null annehmen kann oder nicht (im Gegensatz zu Haskell, wo es keine null-Werte gibt; benötigt man einen Nicht-Wert, wird immer auf Maybe zurückgegriffen).

Options sind aber viel mehr als die bloße Modellierung von nicht vorhandenen Ergebnissen. So läßt sich folgendermaßen ein Defaultwert benutzen:

def greet(name : Option[String]) =
  println("Hallo " + name.getOrElse("Unbekannter"))
greet(Some("Hugo"))
greet(None)

Mittels Patternmatching kann man dafür sorgen, daß None-Werte einfach übersprungen werden, so daß man diese programmatisch nicht mehr berücksichtigen muß:

val namen = List(Some("Hugo"), None, Some("Egon"))
for (Some(n) <- namen) println(n)

Die Box ist keine Standardklasse von Java, sondern stammt aus den Bibliotheken von Lift. Sie erweitert Option um eine Möglichkeit, Fehler zurückzugeben. Eine Box ist entweder Empty, Full(wert) oder enthält Failure(message, ...). Ehe jetzt die Jehowa-Rufe kommen und der Niedergang des Abendlandes beschworen wird: Dies ist nicht die vollständige Abkehr von Exceptions und der Rücksturz in die Steinzeit, als Fehler noch über Rückgabewerte codiert wurden.

Eine Exception bedeutet den unerwarteten Abbruch eines Programmteils und ein „Rücksturz“ bis zu der Stelle, wo mit dem Fehler umgegangen werden kann. Das ist aber nicht immer das gewünschte Verhalten. Beispielsweise wenn man eine Folge von Berechnungen oder Datenbank-Abfragen anstellt, möchte man im Falle eines einzelnen Fehlers möglicherweise nicht den gesamten Vorgang abbrechen, sondern für einen einzelnen Wert die Fehlerinformation hinterlegen und mit der Arbeit fortfahren… um anschließend beispielsweise das Ergebnis auf einer Webseite anzuzeigen (naheliegend, daß Lift ein Webframework ist).