-
Прости образци
-
всичко –
_
option match case Some(x) => x case _ => alternative // matches everything else
-
даване на име –
x
,person
(всеки идентификатор, започващ с малка буква)list match case Nil => "An empty list" case nonEmptyList => s"A list with ${nonEmptyList.size} elements"
-
съпоставяне по тип –
s: String
(придава и име) или_: Int
(проверява типа, но не деклалира променлива)value match case n: Int => n case s: String => s.toInt case d: Double => d.toInt
-
съпоставяне по константа/по обект – произволен литерал:
42
,"abcdef"
и т.н., или константа:Pi
,NumberOfRetries
,Nil
(т.е. идентификатор, започващ с голяма буква). Обикновена променлива може да се използва за константа, ако се загради в "``":`localVariable`
(в противен случай ще се счете за даване на име и ще въведе нова променлива).val perfectNumbers = List(6, 28, 496, 8128) list match case Nil => "Empty list" case `perfectNumbers` => "A list of perfect numbers" case List(math.Pi) => "A list of Pi" case List(42) => "A list of 42" case _ => "Some boring list"
-
-
Съставни образци – деструктурират (разбиват) обект на части, като за всяка част също се посочва образец, който да се приложи на нея (който също може да бъде прост или съставен)
-
наредени n-торки –
(pattern1, pattern2, pattern3, pattern4)
(xs, ys) match case (Nil, Nil) => "Both lists are empty" case (Nil, y :: _) => s"The ys list has at least one element: $y" case _ => "The xs list is not empty"
val (numer, denom) = val div = gcd(n, d) ((n / div) * d.sign, (d / div).abs) // We destructured the tuple into the variables number and denom
-
case class-ове –
Person(pattern1, pattern2, pattern3)
person match case Person("Boyan", age, location) => $"Boyan is $age years old and is from $location" case Person(name, UltimateAnswer, _) => s"A 42 years young person: $name" case Person(name, 34, _) => s"A 34 years young person: $name" case Person(name, _, _) => s"Someone: $name"
-
списъци –
head :: restOfTheList
elements match case first :: second :: rest => s"The first element is $first, the second is $second and there are ${rest.size} more elements" case Nil => "The list is empty" case _ => "There is just one element"
-
колекции –
Seq(first, second, third)
илиSeq(first, second, rest*)
"2022-04-01-hello-there".split("-") match case Array(year, month, day) => s"Year: $year, month: $month, day: $day" case Array(year, month, day, rest*) => s"Year: $year, month: $month, day: $day and some more elements: $rest" case _ => "Not enough elements"
-
Пояснение: всички съставни образци с фиксиран брой елементи са имплементирани чрез метод
unapply
на обекта/придружаващия обект, с който се match-ва, а тези с променлив брой елементи – чрез методunapplySeq
. Обектите с методunapply
илиunapplySeq
се наричат "екстрактори" и включват всички tuple-и, придружаващите обекти на case class-овете (за тях автоматично се генерираunapply
), колекциите, деструктурирането на списъци чрез::
, които описахме по-горе, но може също така да е и всеки произволен от нас обект. Вижте по-надолу секцията за екстрактори.
-
-
Комбинация от име и съставен образец –
person @ Person(name, age, _)
. В ляво посочваме името, а в дясно образец за съпостяване:3434figure match case c @ Circle(radius) => s"Circle $c has radius $radius" case r @ Rectangle(a, b) => s"Rectangle $r has sides $a and $b"
figures match case c @ Circle(radius) :: r @ Rectangle(a, b) :: rest => s"First figure is a circle with radius $radius, second is a rectangle with sides $a and $b and there are ${rest.size} more figure"
-
Алтернативи –
pattern1 | pattern2 | ...
boolean match case "true" | "True" | "TRUE" => true case "false" | "False" | "FALSE" => false case _ => false
-
Guard-ове –
pattern if condition
person match case Person(name, age) if age < 30 => s"Young $name" case Person(name, _) => s"Old $name"
val reciprocal: PartialFunction[Int,Double] = case x if x != 0 => 1.toDouble / x
-
В
match
конструкции, използвайкиcase
блокdef toInteger(value: Int | String | Double): Int = value match case n: Int => n case s: String => s.toInt case d: Double => d.toInt
-
В изрази на места, където се очаква стойност от тип
Function1[A, B]
илиPartialFunction[A, B]
Map(1 -> "one", 2 -> "two").map { case (number, letters) => s"$number: $letters" } // List("1: one", "2: two")
List(Some(1), None, Some(42)).collect { case Some(n) => n * n } // List(1, 1764)
-
При
val
дефиницииval person @ Person(name, age) = findPerson("id-123") val personDescription = s"$name, who is $age years old"
-
При
for
в лявата част на неговите генератори и дефиницииval list1 = List(1, 2, 3, 4, 5) val list2 = List(10, 20, 30, 40, 50) val list3 = List(100, 200) for (a, b) <- list1 zip list2 c <- list3 yield (a + b + c) // List(111, 211, 122, 222, 133, 233, 144, 244, 155, 255)
Pattern-а филтрира само елементите, които го match-ват:
if n == 0 then List(Nil) else for first :: rest <- xs.tails.toList // filters out the empty list restCombination <- combinations(rest, n - 1) yield first :: restCombination
Това изисква наличие на метод
filter
/withFilter
на обекта от дясно. От Scala 3.2 филтриращото действие ще изисква думичкатаcase
пред pattern-а и това изискване ще бъде само в него случай. Повече информация тук. -
В try/catch блок за прихващане на изключения
val parsedResult = try "42L".toInt catch case e: NumberFormatException => 0
По-сложен пример би могъл да се структурира така:
try doSomething() catch case e: Exception1 => alternative1 // this catches more than one exception case _: Exception1 | _: Exception2 => alternative2 // this is using a special extractor that ignores all exceptions that // generally shouldn't be caught, like VirtualMachineError, InterruptedException and others. // see https://scala-lang.org/api/3.x/scala/util/control/NonFatal$.html# for details case NonFatal(e) => alternative3
Екстракторите са обекти, които имат методи unapply
или unapplySeq
и позволяват деструктурирането на части на обекти от определен тип.
object Email:
def unapply(email: String): Option[(String, String)] = email.split("@", -1) match
case Array(name, domain) => Some((name, domain))
case _ => None
List("[email protected]", "boyan@stemma.@", "[email protected]", "vas@[email protected]", "yahoo.com")
.collect {
case Email(name, _) => s"$name's email"
}
// List("zdravko's email", "viktor's email")
unapply
метод бива генериран автоматично за всички case class-ове, благодарение на което всички те могат да бъдат използвани в pattern matching.
object Words:
def unapplySeq(str: String): Option[Seq[String]] =
Some(str.split(" ").toSeq)
val phrase = "the quick brown fox"
val Words(first, second, _*) = phrase
s"$first-$second" // "the-quick"
"the quick brown fox" match
case Words(first, second) => s"Exactly two words: $first, $second"
case Words(_, _, rest*) => s"More than two words, the rest are: $rest"
import scala.util.matching.Regex
val ISODate = new Regex("""(\d{4})-(\d{2})-(\d{2})""")
val ISODate(year, month, day) = "2022-04-13"
Да разгледаме:
def quickSort(xs: List[Int]): List[Int] = xs match
case Nil => Nil
case x :: rest =>
val (smaller, larger) = rest.partition(_ < x)
quickSort(smaller) ::: (x :: quickSort(larger))
Знаем, че при x :: quickSort(larger)
символът ::
е метод на List
. При case x :: rest
символът ::
изглежда като специален синтаксис от Scala. Но това не е така, ::
всъщност е имлементиран в библиотеката на Scala и всеки би могъл да си добави подобна операция при pattern matching. Начинът, по който функционира, е следният. List
в Scala e дефиниран по следния начин:
sealed trait List[+A]
case class ::[+A](head: A, next: List[A]) extends List[A]
case object Nil extends List[Nothing]
Забележете, че Cons класът се казва ::
, като той е case class и това значи, че има unapply
метод. Това значи, че в Pattern Matching можем да пишем:
def quickSort(xs: List[Int]): List[Int] = xs match
case Nil => Nil
case ::(x, rest) =>
val (smaller, larger) = rest.partition(_ < x)
quickSort(smaller) ::: (x :: quickSort(larger))
Точно както при методите обаче, като всичко друго с двама участници, Scala ни позволява да напишем горното и инфиксно, с което получаваме познатия ни синтаксис: x :: rest
.