scalatutorial.de

Stufe 3Objekte und Typen · Lektion 15 von 34

Pattern Matching

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

Pattern Matching ist eines der Werkzeuge, die Scala-Code so kompakt machen. Ein match-Ausdruck vergleicht einen Wert nicht nur mit Konstanten, sondern prüft Typen, zerlegt Datenstrukturen und bindet deren Teile an Namen – in einem Schritt.

Die Grundform

 Scala 3
def beschreibe(x: Any): String = x match
  case 0                => "null"
  case i: Int if i < 0  => "negative Zahl"
  case i: Int           => s"positive Zahl $i"
  case s: String        => s"Text mit ${s.length} Zeichen"
  case _                => "etwas anderes"

Die Fälle werden von oben nach unten geprüft; der erste passende gewinnt. _ passt auf alles. Findet sich kein passender Fall, wirft Scala zur Laufzeit einen MatchError – ein Grund, bei offenen Typen wie Any immer einen Auffangfall zu haben.

Arten von Mustern

MusterBeispielpasst, wenn …
Konstantecase 42, case "ja"der Wert gleich ist
Platzhaltercase _immer
Variablecase ximmer, und bindet den Wert an x
Typcase s: Stringder Wert diesen Typ hat
Alternativecase "j" | "ja"eine der Alternativen passt
case classcase Punkt(x, 0)Typ passt und Felder passen
Tupelcase (a, b)ein Paar vorliegt
Listecase kopf :: restdie Liste nicht leer ist
Guardcase x if x > 10Muster passt und Bedingung erfüllt ist

Kleingeschriebene Namen im Muster sind neue Variablen, großgeschriebene werden als Konstanten gelesen. Wollen Sie mit einem kleingeschriebenen, bereits existierenden Wert vergleichen, setzen Sie ihn in Backticks.

case classes zerlegen

Die eigentliche Stärke zeigt sich bei eigenen Datentypen:

 Scala 3
enum Form:
  case Kreis(radius: Double)
  case Rechteck(breite: Double, hoehe: Double)
  case Dreieck(a: Double, b: Double, c: Double)

import Form.*

def flaeche(f: Form): Double = f match
  case Kreis(r)          => math.Pi * r * r
  case Rechteck(b, h)    => b * h
  case Dreieck(a, b, c)  =>
    val s = (a + b + c) / 2
    math.sqrt(s * (s - a) * (s - b) * (s - c))

Weil Form ein enum ist (oder ein sealed trait), weiß der Compiler, welche Fälle es gibt. Fehlt einer, meldet er eine Warnung: match may not be exhaustive. Das ist ein großer Vorteil gegenüber einer Kette von if-Abfragen: Kommt später eine neue Form hinzu, zeigt der Compiler jede Stelle, die angepasst werden muss.

Verschachtelte Muster

Muster lassen sich beliebig tief verschachteln:

 Scala 3
case class Adresse(ort: String, land: String)
case class Person(name: String, adresse: Option[Adresse])

def herkunft(p: Person): String = p match
  case Person(n, Some(Adresse(ort, "CH"))) => s"$n wohnt in der Schweiz, in $ort"
  case Person(n, Some(Adresse(_, land)))   => s"$n wohnt in $land"
  case Person(n, None)                     => s"Von $n ist keine Adresse bekannt"

Listen und Tupel

 Scala 3
def zusammenfassen(xs: List[Int]): String = xs match
  case Nil               => "leer"
  case x :: Nil          => s"genau ein Element: $x"
  case x :: y :: Nil     => s"zwei Elemente: $x und $y"
  case x :: rest         => s"beginnt mit $x, danach ${rest.size} weitere"

Der Operator :: zerlegt eine Liste in Kopf und Rest, siehe List. Tupel zerlegt man mit runden Klammern – ideal, um mehrere Werte gleichzeitig zu prüfen:

 Scala 3
def spiel(a: String, b: String): String = (a, b) match
  case (x, y) if x == y                                => "unentschieden"
  case ("Stein", "Schere") | ("Schere", "Papier") | ("Papier", "Stein") => "Spieler A gewinnt"
  case _                                               => "Spieler B gewinnt"

Variablen binden mit @

Manchmal wollen Sie ein Muster prüfen und den ganzen Wert behalten. Dafür gibt es @:

 Scala 3
def pruefe(f: Form): String = f match
  case k @ Kreis(r) if r > 100 => s"großer Kreis: $k"
  case andere                  => s"normal: $andere"

Pattern Matching außerhalb von match

Muster tauchen an vielen Stellen auf – überall dort, wo Werte gebunden werden:

 Scala 3
val (x, y) = (3, 4)                         // Zerlegen bei der Definition
val Punkt = raw"(\d+),(\d+)".r
val Punkt(px, py) = "10,20": @unchecked     // regulärer Ausdruck als Muster

for (name, alter) <- List(("Ada", 36), ("Alan", 41)) do println(name)

List(Some(1), None, Some(3)).collect { case Some(n) => n * 10 }   // List(10, 30)

collect nimmt eine partielle Funktion – einen Block aus case-Fällen – und behält nur die Elemente, auf die ein Fall passt. Die Angabe @unchecked sagt dem Compiler, dass Sie wissen, dass das Muster scheitern könnte.

Muster statt instanceof

Wer aus Java kommt, kennt Ketten wie if (x instanceof A) { A a = (A) x; … }. In Scala ersetzt das Typ-Muster diese Konstruktion vollständig. Explizite Umwandlungen mit asInstanceOf braucht man in normalem Scala-Code praktisch nie.

Passt dazu