scalatutorial.de

Stufe 4Funktional denken · Lektion 20 von 34

Option, Some und None

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 2/6 dieser Stufe

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:

 Scala 3
val gefunden: Option[String] = Some("Ada")
val leer: Option[String] = None

Viele Methoden der Standardbibliothek liefern eine Option, wenn ein Ergebnis fehlen kann:

 Scala 3
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    // None

Der 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

 Scala 3
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:

 Scala 3
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.

Option.map: Some wird verwandelt, None läuft unverändert weiterSome(21).map(_ * 2)Some(42)None.map(_ * 2)NoneDie Funktion läuft nur, wenn ein Wert da ist. None geht unverändert durch.
map wirkt nur auf Some; None läuft unverändert durch.
 Scala 3
val eingabe: Option[String] = Some("  ada  ")

eingabe.map(_.trim).map(_.capitalize)   // Some("Ada")
None.map((s: String) => s.trim)         // None

filter behält den Wert nur, wenn eine Bedingung erfüllt ist:

 Scala 3
Some(17).filter(_ >= 18)   // None

Verketten 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:

 Scala 3
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:

 Scala 3
def ortVon(kundenId: Int): Option[String] =
  for
    k    <- kunde(kundenId)
    aId  <- k.adresseId
    adr  <- adresse(aId)
  yield adr.ort

fold: beide Fälle in einem Zug

 Scala 3
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:

 Scala 3
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:

 Scala 3
val eingaben = List("12", "x", "7", "", "30")
eingaben.flatMap(_.toIntOption)    // List(12, 7, 30)
eingaben.map(_.toIntOption).flatten   // dasselbe

Passt dazu