Scala Language
Patroon matching
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!"
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!"
}
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:
- tableswitch : "Ga naar springtabel via index en spring"
- lookupswitch : "Ga naar de springtafel met de sleutelmatch en spring"
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.