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!"

Demo na żywo

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!"
}

Demo na żywo

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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow