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).