Scala Language
Dopasowywanie wzorów
Szukaj…
Składnia
- selektor dopasowuje częściowyFunkcja
- selector match {lista alternatywnych przypadków) // Jest to najczęstsza forma powyższego
Parametry
Parametr | Detale |
---|---|
selektor | Wyrażenie, którego wartość jest dopasowywana do wzorca. |
alternatywy | lista alternatyw ograniczonych case liter. |
Proste dopasowanie wzoru
Ten przykład pokazuje, jak dopasować dane wejściowe do kilku wartości:
def f(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
case _ => "Unknown!"
}
f(2) // "Two"
f(3) // "Unknown!"
Uwaga: _
jest przez spadek lub domyślnie przypadku, ale nie jest to wymagane.
def g(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
}
g(1) // "One"
g(3) // throws a MatchError
Aby uniknąć zgłaszania wyjątku, najlepszą praktyką programowania funkcjonalnego jest tutaj zajmowanie się przypadkiem domyślnym ( case _ => <do something>
). Pamiętaj, że dopasowanie klasy klasy może pomóc kompilatorowi wygenerować ostrzeżenie w przypadku braku sprawy. To samo dotyczy typów zdefiniowanych przez użytkownika, które rozszerzają zapieczętowaną cechę. Jeśli dopasowanie jest całkowite, domyślny przypadek może nie być potrzebny
Możliwe jest także dopasowanie z wartościami, które nie są zdefiniowane bezpośrednio. Muszą to być stabilne identyfikatory , które są uzyskiwane albo przez użycie dużej litery, albo przez wstawianie znaków backsticks.
Z One
i two
zdefiniowane gdzieś indziej lub przekazane jako parametry funkcji:
val One: Int = 1
val two: Int = 2
Można je dopasować w następujący sposób:
def g(x: Int): String = x match {
case One => "One"
case `two` => "Two"
}
W przeciwieństwie do innych języków programowania, takich jak na przykład Java, nie ma możliwości upadku. Jeśli blok sprawy pasuje do danych wejściowych, zostanie wykonany i dopasowanie zostanie zakończone. Dlatego najmniej konkretnym przypadkiem powinien być ostatni blok.
def f(x: Int): String = x match {
case _ => "Default"
case 1 => "One"
}
f(5) // "Default"
f(1) // "Default"
Dopasowywanie wzorów ze stabilnym identyfikatorem
W standardowym dopasowaniu wzorca użyty identyfikator będzie przesłaniał dowolny identyfikator w otaczającym zakresie. Czasami konieczne jest dopasowanie zmiennej obejmującej zasięg.
Poniższa przykładowa funkcja pobiera znak i listę krotek i zwraca nową listę krotek. Jeśli znak istniał jako pierwszy element w jednym z krotek, drugi element jest zwiększany. Jeśli jeszcze nie istnieje na liście, tworzona jest nowa krotka.
def tabulate(char: Char, tab: List[(Char, Int)]): List[(Char, Int)] = tab match {
case Nil => List((char, 1))
case (`char`, count) :: tail => (char, count + 1) :: tail
case head :: tail => head :: tabulate(char, tail)
}
Powyższe pokazuje dopasowanie wzorca, w którym wejście metody char
jest utrzymywane jako „stabilne” w dopasowaniu wzorca: to znaczy, jeśli wywołasz tabulate('x', ...)
, instrukcja pierwszego przypadku będzie interpretowana jako:
case('x', count) => ...
Scala zinterpretuje każdą zmienną wyznaczoną znacznikiem jako stabilny identyfikator: zinterpretuje również każdą zmienną rozpoczynającą się od dużej litery w ten sam sposób.
Dopasowywanie wzorów na Seq
Aby sprawdzić dokładną liczbę elementów w kolekcji
def f(ints: Seq[Int]): String = ints match {
case Seq() =>
"The Seq is empty !"
case Seq(first) =>
s"The seq has exactly one element : $first"
case Seq(first, second) =>
s"The seq has exactly two elements : $first, $second"
case s @ Seq(_, _, _) =>
s"s is a Seq of length three and looks like ${s}" // Note individual elements are not bound to their own names.
case s: Seq[Int] if s.length == 4 =>
s"s is a Seq of Ints of exactly length 4" // Again, individual elements are not bound to their own names.
case _ =>
"No match was found!"
}
Aby wyodrębnić pierwsze elementy i zachować resztę jako kolekcję:
def f(ints: Seq[Int]): String = ints match {
case Seq(first, second, tail @ _*) =>
s"The seq has at least two elements : $first, $second. The rest of the Seq is $tail"
case Seq(first, tail @ _*) =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
// alternative syntax
// here of course this one will never match since it checks
// for the same thing as the one above
case first +: tail =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
case _ =>
"The seq didn't match any of the above, so it must be empty"
}
Zasadniczo dowolna forma, która może być użyta do skonstruowania sekwencji, może być użyta do dopasowania wzorca do istniejącej sekwencji.
Zauważ, że użycie Nil
i ::
będzie działać, gdy wzorzec pasujący do Sekwencji, przekształci go w List
i może mieć nieoczekiwane wyniki. Ogranicz się do Seq( ...)
i +:
aby tego uniknąć.
Pamiętaj, że podczas używania ::
nie będzie działać dla WrappedArray
, Vector
itp., Patrz:
scala> def f(ints:Seq[Int]) = ints match {
| case h :: t => h
| case _ => "No match"
| }
f: (ints: Seq[Int])Any
scala> f(Array(1,2))
res0: Any = No match
I z +:
scala> def g(ints:Seq[Int]) = ints match {
| case h+:t => h
| case _ => "No match"
| }
g: (ints: Seq[Int])Any
scala> g(Array(1,2).toSeq)
res4: Any = 1
Strażnicy (jeśli wyrażenia)
Instrukcje case można łączyć z wyrażeniami if, aby zapewnić dodatkową logikę podczas dopasowywania wzorca.
def checkSign(x: Int): String = {
x match {
case a if a < 0 => s"$a is a negative number"
case b if b > 0 => s"$b is a positive number"
case c => s"$c neither positive nor negative"
}
}
Ważne jest, aby upewnić się, że strażnicy nie utworzą niewyczerpującego dopasowania (kompilator często tego nie złapie):
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case None => doSomethingIfNone
}
To powoduje MatchError
na liczbach nieparzystych. Musisz uwzględnić wszystkie przypadki lub użyć wieloznacznego dopasowania:
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case _ => doSomethingIfNoneOrOdd
}
Dopasowywanie wzorów do klas przypadków
Każda klasa przypadków definiuje ekstraktor, którego można użyć do przechwytywania elementów klasy sprawy podczas dopasowywania wzorca:
case class Student(name: String, email: String)
def matchStudent1(student: Student): String = student match {
case Student(name, email) => s"$name has the following email: $email" // extract name and email
}
Obowiązują wszystkie normalne zasady dopasowywania wzorców - do kontrolowania dopasowywania można używać strażników i wyrażeń stałych:
def matchStudent2(student: Student): String = student match {
case Student("Paul", _) => "Matched Paul" // Only match students named Paul, ignore email
case Student(name, _) if name == "Paul" => "Matched Paul" // Use a guard to match students named Paul, ignore email
case s if s.name == "Paul" => "Matched Paul" // Don't use extractor; use a guard to match students named Paul, ignore email
case Student("Joe", email) => s"Joe has email $email" // Match students named Joe, capture their email
case Student(name, email) if name == "Joe" => s"Joe has email $email" // use a guard to match students named Joe, capture their email
case Student(name, email) => s"$name has email $email." // Match all students, capture name and email
}
Dopasowywanie opcji
Jeśli pasujesz do typu opcji :
def f(x: Option[Int]) = x match {
case Some(i) => doSomething(i)
case None => doSomethingIfNone
}
Jest to funkcjonalnie równoważne z użyciem fold
lub map
/ getOrElse
:
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
Zapieczętowane cechy pasujące do wzoru
Kiedy wzór pasujący do obiektu, którego typem jest zapieczętowana cecha, Scala sprawdzi w czasie kompilacji, czy wszystkie przypadki są „wyczerpująco dopasowane”:
sealed trait Shape
case class Square(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
def matchShape(shape: Shape): String = shape match {
case Square(height, width) => "It's a square"
case Circle(radius) => "It's a circle"
//no case for Point because it would cause a compiler warning.
}
Jeśli później zostanie dodana nowa case class
dla Shape
, wszystkie instrukcje match
w Shape
zaczną generować ostrzeżenie kompilatora. Ułatwia to dokładne refaktoryzowanie: kompilator powiadomi programistę o całym kodzie, który należy zaktualizować.
Dopasowywanie wzorów za pomocą Regex
val emailRegex: Regex = "(.+)@(.+)\\.(.+)".r
"[email protected]" match {
case emailRegex(userName, domain, topDomain) => println(s"Hi $userName from $domain")
case _ => println(s"This is not a valid email.")
}
W tym przykładzie wyrażenie regularne próbuje dopasować podany adres e-mail. Jeśli tak się stanie, userName
i domain
jest pobierany i drukowane. topDomain
również topDomain
, ale w tym przykładzie nic z tym nie zrobiono. Wywołanie .r
na .r
str
jest równoważne z new Regex(str)
. Funkcja r
jest dostępna poprzez niejawną konwersję .
Segregator wzorów (@)
Znak @
wiąże zmienną z nazwą podczas dopasowania wzorca. Zmienna powiązana może być całym dopasowanym obiektem lub częścią dopasowanego obiektu:
sealed trait Shape
case class Rectangle(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
(Circle(5): Shape) match {
case Rectangle(h, w) => s"rectangle, $h x $w."
case Circle(r) if r > 9 => s"large circle"
case c @ Circle(_) => s"small circle: ${c.radius}" // Whole matched object is bound to c
case Point => "point"
}
> res0: String = small circle: 5
Identyfikator powiązany może być używany w filtrach warunkowych. A zatem:
case Circle(r) if r > 9 => s"large circle"
można zapisać jako:
case c @ Circle(_) if c.radius > 9 => s"large circle"
Nazwę można powiązać tylko z częścią dopasowanego wzoru:
Seq(Some(1), Some(2), None) match {
// Only the first element of the matched sequence is bound to the name 'c'
case Seq(c @ Some(1), _*) => head
case _ => None
}
> res0: Option[Int] = Some(1)
Typy dopasowania do wzoru
Dopasowania wzorca można również użyć do sprawdzenia typu instancji, zamiast używania isInstanceOf[B]
:
val anyRef: AnyRef = ""
anyRef match {
case _: Number => "It is a number"
case _: String => "It is a string"
case _: CharSequence => "It is a char sequence"
}
//> res0: String = It is a string
Kolejność spraw jest ważna:
anyRef match {
case _: Number => "It is a number"
case _: CharSequence => "It is a char sequence"
case _: String => "It is a string"
}
//> res1: String = It is a char sequence
W ten sposób jest podobny do klasycznej instrukcji „switch”, bez funkcji awaryjnej. Można jednak również wzorować wartości dopasowania i „wyodrębniać” z danego typu. Na przykład:
case class Foo(s: String)
case class Bar(s: String)
case class Woo(s: String, i: Int)
def matcher(g: Any):String = {
g match {
case Bar(s) => s + " is classy!"
case Foo(_) => "Someone is wicked smart!"
case Woo(s, _) => s + " is adventerous!"
case _ => "What are we talking about?"
}
}
print(matcher(Foo("Diana"))) // prints 'Diana is classy!'
print(matcher(Bar("Hadas"))) // prints 'Someone is wicked smart!'
print(matcher(Woo("Beth", 27))) // prints 'Beth is adventerous!'
print(matcher(Option("Katie"))) // prints 'What are we talking about?'
Zauważ, że w przypadku Foo
i Woo
używamy podkreślenia ( _
) do „dopasowania zmiennej niezwiązanej”. To znaczy, że wartość (w tym przypadku odpowiednio Hadas
i 27
) nie jest powiązana z nazwą, a zatem nie jest dostępna w module obsługi dla tej sprawy. Jest to przydatne, aby dopasować „dowolną” wartość bez obawy o jej wartość.
Dopasowanie wzorca skompilowane jako przełącznik tabeli lub przełącznik wyszukiwania
Adnotacja @switch
informuje kompilator, że instrukcję match
można zastąpić pojedynczą instrukcją tableswitch
na poziomie kodu bajtowego. Jest to niewielka optymalizacja, która może usunąć niepotrzebne porównania i zmienne obciążenia podczas działania.
Adnotacja @switch
działa tylko w przypadku dopasowań względem stałych literalnych i final val
identyfikatorów wartości. Jeśli dopasowania wzorca nie można skompilować jako tableswitch
/ lookupswitch
, kompilator wyświetli ostrzeżenie.
import annotation.switch
def suffix(i: Int) = (i: @switch) match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
Wyniki są takie same jak w przypadku normalnego dopasowania wzorca:
scala> suffix(2)
res1: String = "2nd"
scala> suffix(4)
res2: String = "4th"
Z dokumentacji Scala (2.8+) - @switch
:
Adnotacja do zastosowania do wyrażenia dopasowania. Jeśli jest obecny, kompilator sprawdzi, czy dopasowanie zostało skompilowane z przełącznikiem tabel lub przełącznikiem wyszukiwania, i wygeneruje błąd, jeśli zamiast tego skompiluje się w serię wyrażeń warunkowych.
Ze specyfikacji Java:
- tablewitch : „Dostęp do tabeli skoków przez indeks i skok”
- lookupswitch : „Dostęp do tabeli skoków przez dopasowanie klucza i skok”
Łączenie wielu wzorów jednocześnie
The |
może być użyty do dopasowania pojedynczej instrukcji case do wielu danych wejściowych w celu uzyskania tego samego wyniku:
def f(str: String): String = str match {
case "foo" | "bar" => "Matched!"
case _ => "No match."
}
f("foo") // res0: String = Matched!
f("bar") // res1: String = Matched!
f("fubar") // res2: String = No match.
Pamiętaj, że chociaż dopasowywanie wartości w ten sposób działa dobrze, następujące dopasowanie typów spowoduje problemy:
sealed class FooBar
case class Foo(s: String) extends FooBar
case class Bar(s: String) extends FooBar
val d = Foo("Diana")
val h = Bar("Hadas")
// This matcher WILL NOT work.
def matcher(g: FooBar):String = {
g match {
case Foo(s) | Bar(s) => print(s) // Won't work: s cannot be resolved
case Foo(_) | Bar(_) => _ // Won't work: _ is an unbound placeholder
case _ => "Could not match"
}
}
Jeśli w tym drugim przypadku (z _
) nie potrzebujesz wartości niezwiązanej zmiennej i po prostu chcesz zrobić coś innego, nic ci nie jest:
def matcher(g: FooBar):String = {
g match {
case Foo(_) | Bar(_) => "Is either Foo or Bar." // Works fine
case _ => "Could not match"
}
}
W przeciwnym razie możesz podzielić swoje sprawy:
def matcher(g: FooBar):String = {
g match {
case Foo(s) => s
case Bar(s) => s
case _ => "Could not match"
}
}
Dopasowywanie wzorów na krotkach
Biorąc pod uwagę następującą List
krotek:
val pastries = List(("Chocolate Cupcake", 2.50),
("Vanilla Cupcake", 2.25),
("Plain Muffin", 3.25))
Dopasowywania wzorca można używać do obsługi każdego elementu inaczej:
pastries foreach { pastry => pastry match { case ("Plain Muffin", price) => println(s"Buying muffin for $price") case p if p._1 contains "Cupcake" => println(s"Buying cupcake for ${p._2}") case _ => println("We don't sell that pastry") } }
Pierwszy przypadek pokazuje, jak dopasować do określonego ciągu i uzyskać odpowiednią cenę. Drugi przypadek pokazuje użycie ekstrakcji if i krotki w celu dopasowania do elementów krotki.