Stufe 4Funktional denken · Lektion 20 von 34
Option, Some und None
Der Erfinder der Null-Referenz, Tony Hoare, nannte sie Jahrzehnte später sinngemäß seinen Milliarden-Dollar-Fehler. Scala bietet mit Option einen Typ, der ausdrückt, dass ein Wert fehlen kann – und der Compiler sorgt dafür, dass Sie diesen Fall nicht vergessen.
Ein Wert oder keiner
Option[A] hat genau zwei Formen:
Some(wert)– ein Wert ist vorhanden,None– es gibt keinen Wert.
val gefunden: Option[String] = Some("Ada")
val leer: Option[String] = NoneViele Methoden der Standardbibliothek liefern eine Option, wenn ein Ergebnis fehlen kann:
List(3, 8, 1).find(_ > 5) // Some(8)
List(3, 8, 1).find(_ > 10) // None
Map("de" -> "Berlin").get("fr") // None
"42".toIntOption // Some(42)
List.empty[Int].headOption // NoneDer entscheidende Unterschied zu null: Am Typ sehen Sie, dass der Wert fehlen kann. Ein String ist immer ein String; ein Option[String] erinnert Sie daran, den leeren Fall zu behandeln.
Den Wert herausholen
val name: Option[String] = None
name.getOrElse("unbekannt") // "unbekannt"
name.isDefined // false
name.isEmpty // true
name.contains("Ada") // false
name.orElse(Some("Gast")) // Some("Gast")Mit Pattern Matching:
name match
case Some(n) => println(s"Hallo, $n")
case None => println("Hallo, Unbekannte/r")Umwandeln mit map
Das eigentlich Elegante an Option: Sie können mit dem Wert arbeiten, ohne vorher zu prüfen, ob er da ist.
val eingabe: Option[String] = Some(" ada ")
eingabe.map(_.trim).map(_.capitalize) // Some("Ada")
None.map((s: String) => s.trim) // Nonefilter behält den Wert nur, wenn eine Bedingung erfüllt ist:
Some(17).filter(_ >= 18) // NoneVerketten mit flatMap
Was, wenn der nächste Schritt selbst eine Option liefert? Mit map entstünde eine verschachtelte Option[Option[A]]. flatMap glättet das:
case class Kunde(id: Int, adresseId: Option[Int])
case class Adresse(id: Int, ort: String)
def kunde(id: Int): Option[Kunde] = ???
def adresse(id: Int): Option[Adresse] = ???
def ortVon(kundenId: Int): Option[String] =
kunde(kundenId).flatMap(_.adresseId).flatMap(adresse).map(_.ort)Scheitert irgendein Schritt – Kunde unbekannt, keine Adress-ID, Adresse fehlt –, ist das Ergebnis None. Kein einziges if und kein null-Test nötig. (??? ist übrigens ein Platzhalter, der beim Aufruf eine NotImplementedError wirft – praktisch für Skizzen.)
Option in for-Ausdrücken
Dieselbe Kette liest sich mit einem for-Ausdruck noch klarer:
def ortVon(kundenId: Int): Option[String] =
for
k <- kunde(kundenId)
aId <- k.adresseId
adr <- adresse(aId)
yield adr.ortfold: beide Fälle in einem Zug
val begruessung = name.fold("Hallo, Gast")(n => s"Hallo, $n")Das erste Argument ist der Wert für None, die Funktion im zweiten wird für Some angewendet.
Option und Java
Java-Bibliotheken liefern oft null. Wickeln Sie solche Werte gleich an der Grenze ein:
val home: Option[String] = Option(System.getenv("HOME"))Option(x) – mit großem O als Aufruf – ergibt None, wenn x null ist, sonst Some(x). Ab dieser Stelle ist Ihr Code frei von null.
Option in Collections
Eine Option verhält sich wie eine Sammlung mit null oder einem Element. Das macht Kombinationen mit Listen angenehm:
val eingaben = List("12", "x", "7", "", "30")
eingaben.flatMap(_.toIntOption) // List(12, 7, 30)
eingaben.map(_.toIntOption).flatten // dasselbe