Zoeken…


Syntaxis

  • selector match gedeeltelijke functie
  • selector match {lijst met case-alternatieven) // Dit is de meest voorkomende vorm van het bovenstaande

parameters

Parameter Details
keuzeschakelaar De uitdrukking waarvan de waarde patroon-gematcht is.
alternatieven een lijst met case gescheiden alternatieven.

Eenvoudig patroon Match

Dit voorbeeld laat zien hoe een invoer kan worden vergeleken met verschillende waarden:

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

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

Live demonstratie

Opmerking: _ is de doorval of standaardgeval , maar dit is niet vereist.

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

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

Om te voorkomen dat er een uitzondering wordt geworpen, is het een goede praktijk om hier functioneel te programmeren om het standaardgeval te behandelen ( case _ => <do something> ). Merk op dat het matchen van een case-klasse de compiler kan helpen een waarschuwing te produceren als een case ontbreekt. Hetzelfde geldt voor door de gebruiker gedefinieerde typen die een verzegelde eigenschap uitbreiden. Als de match totaal is, is een standaardgeval misschien niet nodig

Het is ook mogelijk om te matchen met waarden die niet inline zijn gedefinieerd. Dit moeten stabiele identificatiemiddelen zijn , die worden verkregen door ofwel een naam met een hoofdletter te gebruiken of backticks in te sluiten.

Met One en two ergens anders gedefinieerd of doorgegeven als functieparameters:

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

Ze kunnen op de volgende manier worden vergeleken:

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

In tegenstelling tot andere programmeertalen zoals Java is er bijvoorbeeld geen doorval. Als een casusblok overeenkomt met een invoer, wordt deze uitgevoerd en is de matching voltooid. Daarom moet het minst specifieke geval het laatste gevalblok zijn.

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

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

Patroonaanpassing met stabiele identificatie

Bij standaard patroonmatching zal de gebruikte identifier elke identifier in de omhullende scope overschaduwen. Soms is het nodig om overeen te komen met de variabele van de omhullende scope.

De volgende voorbeeldfunctie neemt een karakter en een lijst met tupels en retourneert een nieuwe lijst met tupels. Als het karakter bestond als het eerste element in een van de tupels, wordt het tweede element verhoogd. Als het nog niet bestaat in de lijst, wordt een nieuwe tuple gemaakt.

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

Het bovenstaande toont patroonovereenkomst waarbij de invoer van de methode, char , 'stabiel' wordt gehouden in de patroonovereenkomst: dat wil zeggen, als u tabulate('x', ...) aanroept, zou de eerste casusverklaring worden geïnterpreteerd als:

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

Scala interpreteert elke met een vinkje afgebakende variabele als een stabiele identificatie: het interpreteert ook elke variabele die met een hoofdletter begint op dezelfde manier.

Patroonovereenkomst op een Seq

Om te controleren op een exact aantal elementen in de verzameling

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

Live demonstratie

Om de eerste (n) element (en) te extraheren en de rest als een verzameling te behouden:

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 het algemeen kan elke vorm die kan worden gebruikt om een sequentie te construeren worden gebruikt om een patroon te matchen met een bestaande sequentie.

Merk op dat terwijl het gebruik van Nil en :: werkt wanneer het patroon overeenkomt met een reeks, het deze wel omzet in een List en onverwachte resultaten kan hebben. Beperk jezelf tot Seq( ...) en +: om dit te voorkomen.

Merk op dat tijdens het gebruik van :: niet werkt voor WrappedArray , Vector enz., Zie:

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

En met +:

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

Guards (als uitdrukkingen)

Case-statements kunnen worden gecombineerd met if-expressies om extra logica te bieden bij het matchen van patronen.

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

Het is belangrijk om ervoor te zorgen dat je bewakers geen niet-uitputtende match maken (de compiler zal dit vaak niet vangen):

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

Dit werpt een MatchError op oneven nummers. U moet rekening houden met alle gevallen of een wildcard-overeenkomstgeval gebruiken:

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

Patroon matching met Case Classes

Elke case-klasse definieert een extractor die kan worden gebruikt om de leden van de case-klasse vast te leggen bij het matchen van patronen:

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
}

Alle normale regels voor patroonovereenkomst zijn van toepassing - u kunt bewakers en constante uitdrukkingen gebruiken om het matchen te regelen:

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 
}

Overeenkomen met een optie

Als u overeenkomt met een optietype :

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

Dit komt functioneel overeen met het gebruik van fold of map / getOrElse :

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

Patroon bijpassende verzegelde eigenschappen

Wanneer een patroon overeenkomt met een object waarvan het type een verzegelde eigenschap is, zal Scala tijdens het compileren controleren of alle gevallen 'volledig overeenkomen':

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

Als er een nieuwe case class voor Shape later wordt toegevoegd, alle match uitspraken over Shape zal beginnen om een compiler waarschuwing gooien. Dit maakt grondige refactoring eenvoudiger: de compiler waarschuwt de ontwikkelaar voor alle code die moet worden bijgewerkt.

Patroonovereenkomst met 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 dit voorbeeld probeert de regex het opgegeven e-mailadres te matchen. Als dat zo is, de userName en domain wordt gehaald en afgedrukt. topDomain wordt ook geëxtraheerd, maar er wordt in dit voorbeeld niets mee gedaan. Het aanroepen van .r op een String str is gelijk aan new Regex(str) . De functie r is beschikbaar via een impliciete conversie .

Patroonbinder (@)

Het @ -teken bindt een variabele aan een naam tijdens een patroonmatch. De gebonden variabele kan het gehele overeenkomende object zijn of een deel van het overeenkomende object:

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

De gebonden identifier kan worden gebruikt in voorwaardelijke filters. Dus:

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

kan worden geschreven als:

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

De naam kan slechts aan een deel van het overeenkomende patroon worden gebonden:

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)

Patroon overeenkomende typen

Patroonovereenkomst kan ook worden gebruikt om het type van een instantie te controleren, in plaats van 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

De volgorde van de zaken is belangrijk:

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

Op deze manier lijkt het op een klassieke 'schakelaar'-verklaring, zonder de doorvalfunctionaliteit. U kunt echter ook patroonovereenkomst en 'extraheren' waarden van het betreffende type. Bijvoorbeeld:

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

Merk op dat we in het geval Foo en Woo het onderstrepingsteken ( _ ) gebruiken om 'overeen te komen met een ongebonden variabele'. Dat wil zeggen dat de waarde (in dit geval Hadas en 27 respectievelijk) niet aan een naam is gebonden en dus niet beschikbaar is in de handler voor dat geval. Dit is handig steno om 'elke' waarde te matchen zonder je zorgen te maken over wat die waarde is.

Patroonovereenkomst gecompileerd als tabelschakelaar of opzoekschakelaar

De annotatie @switch vertelt de compiler dat het match statement kan worden vervangen door een enkele tableswitch instructie op bytecode-niveau. Dit is een kleine optimalisatie die onnodige vergelijkingen en variabele belastingen tijdens runtime kan verwijderen.

De annotatie @switch werkt alleen voor wedstrijden tegen letterlijke constanten en final val id's. Als de patroonovereenkomst niet kan worden gecompileerd als een tableswitch / lookupswitch , geeft de compiler een waarschuwing.

import annotation.switch

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

De resultaten zijn hetzelfde als bij een normale patroonovereenkomst:

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

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

Uit de Scala-documentatie (2.8+) - @switch :

Een annotatie die moet worden toegepast op een overeenkomstuitdrukking. Indien aanwezig, zal de compiler verifiëren dat de wedstrijd is gecompileerd met een tabelschakelaar of opzoekschakelaar en een foutmelding geven als deze in plaats daarvan wordt gecompileerd in een reeks voorwaardelijke uitdrukkingen.

Van de Java-specificatie:

Meerdere patronen tegelijk matchen

De | kan worden gebruikt om een enkele case-statement te matchen met meerdere ingangen om hetzelfde resultaat te krijgen:

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.

Merk op dat hoewel het matchen van waarden op deze manier goed werkt, de volgende matching van typen problemen zal veroorzaken:

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

Als je in het laatste geval (met _ ) de waarde van de ongebonden variabele niet nodig hebt en gewoon iets anders wilt doen, dan is het prima:

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

Anders blijf je de zaken splitsen:

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

Patroonaanpassing op tupels

Gegeven de volgende List met tupels:

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

Patroonafstemming kan worden gebruikt om elk element anders te behandelen:

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

Het eerste geval laat zien hoe te matchen met een specifieke string en de bijbehorende prijs te krijgen. Het tweede geval toont een gebruik van de extractie van if en tuple om te matchen met elementen van de tuple.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow