Von Möglichkeiten und Kisten

Die Krux mit der Mehrdeutigkeit von null

Beim Ent­wi­ckeln stol­per­te ich neu­lich (nicht zum ers­ten Mal) über fol­gen­de Si­tua­ti­on: Aus einer Map wur­den Daten ge­le­sen; die Ein­trä­ge waren In­stan­zen einer Klas­se, mit­un­ter mußte aber aus­ge­drückt wer­den, daß es keine Zu­ord­nung gab – es mach­te in die­sem Fall tat­säch­lich einen Un­ter­schied, ob es kei­nen Ein­trag in der Map gab, oder ob der Ver­weis auf „kein Wert“ ging. Gerne ver­wen­det man in Java für „kein Wert“ eben null, was aber ge­ra­de im Fall einer Map zu fol­gen­der Feh­ler­quel­le führt:

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

Der Code wirft die bei­den Fälle zu­sam­men; will man sie un­ter­schei­den, muß man zu­nächst mit einem map.containsKey(..) prü­fen, ob die Map einen ent­spre­chen­den Ein­trag be­sitzt. Kon­se­quenz ist, daß unter der Haube zwei Mal auf die Hash­ta­bel­le zu­ge­grif­fen wer­den muß.

Scala bie­tet mit der Typ­klas­se Option die Mög­lich­keit, für jeden be­lie­bi­gen be­ste­hen­den Typ einen sol­chen „Null­wert“ zu mo­del­lie­ren (das ana­lo­ge Kon­zept fin­det man mei­nes Wis­sens in Has­kell mit dem Da­ten­typ Maybe). Werte vom Typ Option[MyData] sind ent­we­der Some[MyData] wenn sie einen tat­säch­li­chen Wert re­prä­sen­tie­ren, oder eben None. Im obi­gen Bei­spiel hätte man sich also damit be­hel­fen kön­nen, in der Map nicht Werte vom Typ MyData, son­dern vom Typ Option[MyData] ab­zu­le­gen.

Zu­ge­ge­be­ner­ma­ßen ist das ein eher pa­tho­lo­gi­scher Ein­satz­zweck; viel häu­fi­ger wird Option zur Ver­mei­dung von null-Wer­ten ver­wen­det – wes­halb ein Some(null) leicht­sam als Trol­le­rei auf­ge­fa­ßt wird :-)

(Quel­le des Co­mics) Letzt­end­lich braucht man also auch hier eine Kon­ven­ti­on, ob ein Wert null an­neh­men kann oder nicht (im Ge­gen­satz zu Has­kell, wo es keine null-Wer­te gibt; be­nö­tigt man einen Nicht-Wert, wird immer auf Maybe zu­rück­ge­grif­fen).

Op­ti­ons sind aber viel mehr als die bloße Mo­del­lie­rung von nicht vor­han­de­nen Er­geb­nis­sen. So läßt sich fol­gen­der­ma­ßen ein De­fault­wert be­nut­zen:

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

Mit­tels Pat­tern­matching kann man dafür sor­gen, daß No­ne-Wer­te ein­fach über­sprun­gen wer­den, so daß man diese pro­gram­ma­tisch nicht mehr be­rück­sich­ti­gen muß:

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

Die Box ist keine Stan­dard­klas­se von Java, son­dern stammt aus den Bi­blio­the­ken von Lift. Sie er­wei­tert Op­ti­on um eine Mög­lich­keit, Feh­ler zu­rück­zu­ge­ben. Eine Box ist ent­we­der Empty, Full(wert) oder ent­hält Failure(message, ...). Ehe jetzt die Jehowa-Ru­fe kom­men und der Nie­der­gang des Abend­lan­des be­schwo­ren wird: Dies ist nicht die voll­stän­di­ge Ab­kehr von Ex­cep­ti­ons und der Rück­s­turz in die Stein­zeit, als Feh­ler noch über Rück­ga­be­wer­te co­diert wur­den.

Eine Ex­cep­ti­on be­deu­tet den un­er­war­te­ten Ab­bruch eines Pro­gramm­teils und ein „Rück­s­turz“ bis zu der Stel­le, wo mit dem Feh­ler um­ge­gan­gen wer­den kann. Das ist aber nicht immer das ge­wünsch­te Ver­hal­ten. Bei­spiels­wei­se wenn man eine Folge von Be­rech­nun­gen oder Da­ten­bank-Ab­fra­gen an­stellt, möch­te man im Falle eines ein­zel­nen Feh­lers mög­li­cher­wei­se nicht den ge­sam­ten Vor­gang ab­bre­chen, son­dern für einen ein­zel­nen Wert die Feh­ler­in­for­ma­ti­on hin­ter­le­gen und mit der Ar­beit fort­fah­ren… um an­schlie­ßend bei­spiels­wei­se das Er­geb­nis auf einer Web­sei­te an­zu­zei­gen (na­he­lie­gend, daß Lift ein Web­frame­work ist).