Scala Language
Mönstermatchning
Sök…
Syntax
- väljarmatch partiell funktion
- väljarmatch {lista över fallalternativ) // Detta är den vanligaste formen av ovanstående
parametrar
Parameter | detaljer |
---|---|
väljare | Uttrycket vars värde mönstermatchas. |
alternativ | en lista över alternativ som är avgränsade från case . |
Enkel mönster match
Detta exempel visar hur man matchar en ingång mot flera värden:
def f(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
case _ => "Unknown!"
}
f(2) // "Two"
f(3) // "Unknown!"
Obs: _
är fallfallet eller standardfallet , men det krävs inte.
def g(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
}
g(1) // "One"
g(3) // throws a MatchError
För att undvika att kasta ett undantag är det här den bästa funktionella programmeringspraxisen här att hantera standardfallet ( case _ => <do something>
). Observera att matchning över en fallklass kan hjälpa kompilatorn att producera en varning om ett fall saknas. Detsamma gäller för användardefinierade typer som utvidgar en förseglad egenskap. Om matchen är total kan det inte behövas ett standardfall
Det är också möjligt att matcha mot värden som inte är definierade inline. Dessa måste vara stabila identifierare , som erhålls antingen med hjälp av ett stort namn eller med bifogade backticks.
Med One
och two
definierade någon annanstans, eller skickas som funktionsparametrar:
val One: Int = 1
val two: Int = 2
De kan matchas mot på följande sätt:
def g(x: Int): String = x match {
case One => "One"
case `two` => "Two"
}
Till skillnad från andra programmeringsspråk som Java till exempel finns det inget fall. Om ett fallblock matchar en ingång, körs det och matchningen är klar. Därför bör det minst specifika fallet vara det sista fallblocket.
def f(x: Int): String = x match {
case _ => "Default"
case 1 => "One"
}
f(5) // "Default"
f(1) // "Default"
Mönstermatchning med stabil identifierare
Vid standardmönstermatchning kommer den identifierare som används skuggar alla identifierare i det bifogade omfånget. Ibland är det nödvändigt att matcha variabeln i det bifogade omfånget.
Följande exempelfunktion tar ett tecken och en lista med tuplingar och returnerar en ny lista med tuplingar. Om karaktären fanns som det första elementet i en av tupplarna ökas det andra elementet. Om den inte ännu finns i listan skapas en ny tupel.
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)
}
Ovanstående visar mönstermatchning där metodens inmatning, char
, hålls "stabil" i mönstermatchningen: det vill säga om du kallar tabulate('x', ...)
, skulle det första fallet uttalas som:
case('x', count) => ...
Scala kommer att tolka alla variabler som är avgränsade med ett fästmärke som en stabil identifierare: det kommer också att tolka alla variabler som börjar med en stor bokstav på samma sätt.
Mönstermatchning på en sekvens
För att kontrollera för ett exakt antal element i samlingen
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!"
}
För att extrahera de första elementen och behålla resten som en samling:
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"
}
I allmänhet kan vilken form som helst som kan användas för att konstruera en sekvens användas för att mönstermatchning mot en befintlig sekvens.
Observera att när du använder Nil
och ::
kommer att fungera när mönster matchar en sekvens, konverterar den den till en List
och kan ha oväntade resultat. Begränsa dig till Seq( ...)
och +:
att undvika detta.
Observera att medan du använder ::
inte fungerar för WrappedArray
, Vector
etc, se:
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
Och med +:
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
Vakter (om uttryck)
Falluttalanden kan kombineras med uttryck för att ge extra logik när mönstermatchning.
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"
}
}
Det är viktigt att se till att dina vakter inte skapar en uttömmande matchning (kompilatorn kommer ofta inte att fånga detta):
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case None => doSomethingIfNone
}
Detta kastar en MatchError
på udda nummer. Du måste antingen redogöra för alla fall eller använda ett jokerteckenmatch:
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case _ => doSomethingIfNoneOrOdd
}
Mönstermatchning med fallklasser
Varje fallklass definierar en extraherare som kan användas för att fånga medlemmarna i fallklassen när mönstermatchning:
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
}
Alla normala regler för mönster-matchning gäller - du kan använda skydd och konstant uttryck för att kontrollera matchningen:
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
}
Matchning på ett alternativ
Om du matchar en alternativtyp :
def f(x: Option[Int]) = x match {
case Some(i) => doSomething(i)
case None => doSomethingIfNone
}
Detta är funktionellt ekvivalent med att använda fold
, eller map
/ getOrElse
:
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
Mönster som matchar förseglade drag
När mönster matchar ett objekt vars typ är ett förseglat drag kommer Scala att kontrollera vid sammanställningen att alla fall är "uttömmande matchade":
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.
}
Om ett nytt case class
för Shape
senare läggs alla match
uttalanden om Shape
kommer att börja kasta en kompilator varning. Detta underlättar grundlig refactoring: kompilatorn varnar utvecklaren för all kod som måste uppdateras.
Mönstermatchning med 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.")
}
I det här exemplet försöker regexet matcha den angivna e-postadressen. Om det gör det extraheras och skrivs ut userName
och domain
. topDomain
extraheras också, men ingenting görs med det i det här exemplet. Att ringa .r
på en str
motsvarar new Regex(str)
. Funktionen r
är tillgänglig via en implicit konvertering .
Mönsterbindemedel (@)
@
-Tecknet binder en variabel till ett namn under en mönstermatchning. Den bundna variabeln kan antingen vara hela det matchade objektet eller en del av det matchade objektet:
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
Den bundna identifieraren kan användas i villkorade filter. Således:
case Circle(r) if r > 9 => s"large circle"
kan skrivas som:
case c @ Circle(_) if c.radius > 9 => s"large circle"
Namnet kan endast bindas till en del av det matchade mönstret:
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)
Mönster matchande typer
Mönstermatchning kan också användas för att kontrollera typ av instans snarare än att använda 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
Fallens ordning är viktig:
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
På detta sätt liknar det ett klassiskt "switch" -uttalande, utan att det går igenom funktionaliteten. Du kan dock också mönstermatchning och "extrahera" värden från den aktuella typen. Till exempel:
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?'
Observera att i Foo
och Woo
fallet använder vi understrecket ( _
) för att "matcha en obunden variabel". Det vill säga att värdet (i detta fall Hadas
respektive 27
) inte är bundet till ett namn och därför inte är tillgängligt i hanteraren för det fallet. Detta är användbart kortfattat för att matcha "valfritt" värde utan att oroa dig för vad detta värde är.
Mönstermatchning kompilerad som bordswitch eller lookupwitch
Den @switch
anteckning berättar kompilatorn att match
uttalande kan ersättas med en enda tableswitch
instruktion på bytekod nivå. Detta är en mindre optimering som kan ta bort onödiga jämförelser och variabel belastning under körning.
@switch
anteckningen fungerar endast för matchningar mot bokstavliga konstanter och final val
. Om mönstermatchningen inte kan kompileras som en tableswitch
/ lookupswitch
, kommer kompilatorn att ta upp en varning.
import annotation.switch
def suffix(i: Int) = (i: @switch) match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
Resultaten är desamma som en vanlig mönstermatchning:
scala> suffix(2)
res1: String = "2nd"
scala> suffix(4)
res2: String = "4th"
Från Scala-dokumentationen (2.8+) - @switch
:
En kommentar som ska tillämpas på ett matchningsuttryck. Om det finns, kommer kompilatorn att verifiera att matchen har sammanställts till en bordswitch eller lookup-witch och ger ett fel om det istället sammanställs till en serie villkorliga uttryck.
Från Java-specifikationen:
- tableswitch : "Åtkomst till hoppbord efter index och hopp"
- lookupswitch : "Åtkomst till hoppbord efter nyckelmatchning och hopp"
Matcha flera mönster på en gång
Den |
kan användas för att ha en enstaka uttalande matchning mot flera ingångar för att ge samma resultat:
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.
Observera att även om matchning av värden på detta sätt fungerar bra, kommer följande matchning av typer att orsaka problem:
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"
}
}
Om du i det senare fallet (med _
) inte behöver värdet på den obundna variabeln och bara vill göra något annat, har du det bra:
def matcher(g: FooBar):String = {
g match {
case Foo(_) | Bar(_) => "Is either Foo or Bar." // Works fine
case _ => "Could not match"
}
}
Annars står du kvar med att dela dina ärenden:
def matcher(g: FooBar):String = {
g match {
case Foo(s) => s
case Bar(s) => s
case _ => "Could not match"
}
}
Mönster Matchning på tuples
Följande List
med tuples:
val pastries = List(("Chocolate Cupcake", 2.50),
("Vanilla Cupcake", 2.25),
("Plain Muffin", 3.25))
Mönstermatchning kan användas för att hantera varje element på olika sätt:
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") } }
Det första fallet visar hur man matchar mot en specifik sträng och får motsvarande pris. Det andra fallet visar användning av if och tuple extraktion för att matcha mot element i tupeln.