Ricerca…


Sintassi

  • selettore corrisponde a partialFunction
  • selettore match {elenco di alternative di casi) // Questa è la forma più comune di quanto sopra

Parametri

Parametro Dettagli
selettore L'espressione il cui valore è associato al modello.
alternative un elenco di alternative minime di case .

Match Pattern semplice

Questo esempio mostra come abbinare un input a più valori:

def f(x: Int): String = x match {
  case 1 => "One"
  case 2 => "Two"
  case _ => "Unknown!"
}

f(2)  // "Two"
f(3)  // "Unknown!"

Dimostrazione dal vivo

Nota: _ è la caduta attraverso o caso di default, ma non è necessario.

def g(x: Int): String = x match {
  case 1 => "One"
  case 2 => "Two"
}

g(1)  // "One"
g(3)  // throws a MatchError

Per evitare di lanciare un'eccezione, qui è la migliore pratica di programmazione funzionale per gestire il caso predefinito ( case _ => <do something> ). Si noti che la corrispondenza su una classe di case può aiutare il compilatore a produrre un avvertimento se manca un caso. Lo stesso vale per i tipi definiti dall'utente che estendono un tratto sigillato. Se la corrispondenza è totale, potrebbe non essere necessario un caso predefinito

È anche possibile confrontarsi con valori che non sono definiti in linea. Questi devono essere identificatori stabili , che si ottengono usando un nome in maiuscolo o racchiudendo i backtick.

Con One e two definiti da qualche altra parte o passati come parametri di funzione:

val One: Int = 1
val two: Int = 2

Possono essere abbinati nel modo seguente:

def g(x: Int): String = x match {
  case One => "One"
  case `two` => "Two"
}

A differenza di altri linguaggi di programmazione come Java, ad esempio, non vi è alcuna caduta. Se un blocco di casi corrisponde a un input, viene eseguito e la corrispondenza è finita. Pertanto il caso meno specifico dovrebbe essere l'ultimo blocco del caso.

def f(x: Int): String = x match {
  case _ => "Default"
  case 1 => "One"
}

f(5) // "Default"
f(1) // "Default"

Pattern Matching With Stable Identifier

Nella corrispondenza del modello standard, l'identificatore utilizzato ombreggia qualsiasi identificatore nell'ambito di inclusione. A volte è necessario abbinare la variabile dello scope che racchiude.

La seguente funzione di esempio prende un carattere e un elenco di tuple e restituisce un nuovo elenco di tuple. Se il carattere esiste come primo elemento in una delle tuple, il secondo elemento viene incrementato. Se non esiste ancora nella lista, viene creata una nuova tupla.

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)
}

Quanto sopra dimostra la corrispondenza del modello in cui l'input del metodo, char , viene mantenuto "stabile" nella corrispondenza del modello: ovvero, se si chiama tabulate('x', ...) , la prima dichiarazione case verrà interpretata come:

case('x', count) => ...

Scala interpreterà qualsiasi variabile demarcata con un segno di spunta come identificatore stabile: interpreterà anche qualsiasi variabile che inizia con una lettera maiuscola nello stesso modo.

Pattern Matching su un Seq

Per verificare un numero preciso di elementi nella collezione

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

Dimostrazione dal vivo

Per estrarre il (i) primo (i) elemento (i) e mantenere il resto come una raccolta:

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

In generale, qualsiasi forma che può essere usata per costruire una sequenza può essere usata per modellare la corrispondenza con una sequenza esistente.

Si noti che durante l'utilizzo di Nil e :: funzionerà quando il modello corrisponde a una sequenza, lo converte in una List e può avere risultati imprevisti. Vincolati a Seq( ...) e +: per evitare questo.

Nota che mentre usi :: non funzionerà per WrappedArray , Vector ecc., Vedi:

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

E con +:

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

Guardie (se espressioni)

Le espressioni case possono essere combinate con le espressioni if ​​per fornire una logica extra durante la corrispondenza dei pattern.

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

È importante assicurarsi che le tue guardie non creino una corrispondenza non esaustiva (il compilatore spesso non lo prenderà):

def f(x: Option[Int]) = x match {
    case Some(i) if i % 2 == 0 => doSomething(i)
    case None    => doSomethingIfNone
}

Questo genera un MatchError su numeri dispari. È necessario tenere conto di tutti i casi o utilizzare una casella di ricerca con caratteri jolly:

def f(x: Option[Int]) = x match {
    case Some(i) if i % 2 == 0 => doSomething(i)
    case _ => doSomethingIfNoneOrOdd
}

Pattern matching con classi di casi

Ogni classe case definisce un estrattore che può essere usato per catturare i membri della classe case quando il pattern matching:

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
}

Vengono applicate tutte le normali regole di corrispondenza del modello: puoi utilizzare guardie ed espressioni costanti per controllare la corrispondenza:

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 
}

Abbinamento su un'opzione

Se stai abbinando su un tipo di opzione :

def f(x: Option[Int]) = x match {
    case Some(i) => doSomething(i)
    case None    => doSomethingIfNone
}

Questo è funzionalmente equivalente all'uso di fold o map / getOrElse :

def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)

Tratti sigillati che corrispondono al modello

Quando pattern corrisponde a un oggetto il cui tipo è un tratto sigillato, Scala verificherà in fase di compilazione che tutti i casi siano "esaustivamente abbinati":

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.
}

Se in seguito viene aggiunta una nuova case class per Shape , tutte le istruzioni di match su Shape inizieranno a generare un avviso del compilatore. Ciò facilita il completo refactoring: il compilatore avviserà lo sviluppatore di tutto il codice che deve essere aggiornato.

Pattern Matching con 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.")
}

In questo esempio, la regex tenta di abbinare l'indirizzo email fornito. In caso userName , userName e domain vengono estratti e stampati. topDomain viene estratto, ma in questo esempio non viene eseguito nulla. Chiamare .r su una stringa str equivale a new Regex(str) . La funzione r è disponibile tramite una conversione implicita .

Pattern binder (@)

Il segno @ associa una variabile a un nome durante una corrispondenza di modello. La variabile associata può essere l'intero oggetto abbinato o parte dell'oggetto abbinato:

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

L'identificatore associato può essere utilizzato nei filtri condizionali. Così:

case Circle(r) if r > 9 => s"large circle"

può essere scritto come:

case c @ Circle(_) if c.radius > 9 => s"large circle"

Il nome può essere associato solo a una parte del modello abbinato:

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)

Tipi di corrispondenza del modello

La corrispondenza del modello può anche essere utilizzata per verificare il tipo di un'istanza, piuttosto che utilizzare 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

L'ordine dei casi è importante:

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

In questo modo è simile a una classica istruzione "switch", senza la funzionalità fall-through. Tuttavia, puoi anche creare pattern match e "estrai" dal tipo in questione. Per esempio:

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?'

Si noti che nel caso Foo e Woo usiamo il carattere di sottolineatura ( _ ) per "abbinare una variabile non associata". Ciò significa che il valore (in questo caso Hadas e 27 , rispettivamente) non è associato a un nome e quindi non è disponibile nel gestore per quel caso. Questa è una utile stenografia per abbinare il valore "qualsiasi" senza preoccuparsi di quale sia quel valore.

Pattern Matching compilato come tableswitch o lookupswitch

L'annotazione @switch indica al compilatore che l'istruzione match può essere sostituita con una singola istruzione tableswitch a livello bytecode. Questa è una piccola ottimizzazione che può rimuovere confronti non necessari e carichi variabili durante il runtime.

L'annotazione @switch funziona solo per le corrispondenze con costanti letterali e identificatori final val . Se il pattern match non può essere compilato come un tableswitch / lookupswitch , il compilatore solleverà un avvertimento.

import annotation.switch

def suffix(i: Int) = (i: @switch) match {
  case 1 => "st"
  case 2 => "nd"
  case 3 => "rd"
  case _ => "th"
}

I risultati sono gli stessi di un normale pattern match:

scala> suffix(2)
res1: String = "2nd"

scala> suffix(4)
res2: String = "4th"

Dalla documentazione di Scala (2.8+) - @switch :

Un'annotazione da applicare a un'espressione di corrispondenza. Se presente, il compilatore verificherà che la corrispondenza sia stata compilata su un tableswitch o su un interruttore di ricerca e genera un errore se invece compila in una serie di espressioni condizionali.

Dalla specifica Java:

  • tableswitch : "Accedi alla tabella di salto per indice e salta"
  • lookupswitch : "Accedi alla tabella di salto per corrispondenza e salto"

Abbinamento di più pattern contemporaneamente

Il | può essere utilizzato per avere una singola istruzione di dichiarazione del caso contro più input per ottenere lo stesso risultato:

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.

Si noti che mentre la corrispondenza dei valori in questo modo funziona bene, la seguente corrispondenza dei tipi causerà problemi:

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

Se nel secondo caso (con _ ) non hai bisogno del valore della variabile non associata e vuoi solo fare qualcos'altro, stai bene:

def matcher(g: FooBar):String = {
  g match {
    case Foo(_) | Bar(_) => "Is either Foo or Bar."  // Works fine
    case _ => "Could not match"
  }
}

Altrimenti, hai lasciato a spaccare i tuoi casi:

def matcher(g: FooBar):String = {
  g match {
    case Foo(s) => s 
    case Bar(s) => s
    case _ => "Could not match"
  }
}

Pattern Matching su tuple

Dato il seguente List di tuple:

val pastries = List(("Chocolate Cupcake", 2.50), 
                    ("Vanilla Cupcake", 2.25),
                    ("Plain Muffin", 3.25))

La corrispondenza del modello può essere utilizzata per gestire ogni elemento in modo diverso:

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

Il primo caso mostra come abbinare una stringa specifica e ottenere il prezzo corrispondente. Il secondo caso mostra un uso dell'estrazione if e tuple per confrontarsi con gli elementi della tupla.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow