Scala Language
La coincidencia de patrones
Buscar..
Sintaxis
- selector match partialFunction
- selector de coincidencia {lista de alternativas de casos) // Esta es la forma más común de las anteriores
Parámetros
Parámetro | Detalles |
---|---|
selector | La expresión cuyo valor se corresponde con el patrón. |
alternativas | una lista de alternativas delimitadas por case . |
Coincidencia de patrón simple
Este ejemplo muestra cómo hacer coincidir una entrada con varios valores:
def f(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
case _ => "Unknown!"
}
f(2) // "Two"
f(3) // "Unknown!"
Nota: _
es la caída a través o por defecto caso, pero no es necesario.
def g(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
}
g(1) // "One"
g(3) // throws a MatchError
Para evitar lanzar una excepción, es una mejor práctica de programación funcional aquí manejar el caso predeterminado ( case _ => <do something>
). Tenga en cuenta que hacer coincidir una clase de caso puede ayudar al compilador a producir una advertencia si falta un caso. Lo mismo ocurre con los tipos definidos por el usuario que extienden un rasgo sellado. Si la coincidencia es total, puede que no sea necesario un caso predeterminado
También es posible hacer coincidencias con valores que no están definidos en línea. Estos deben ser identificadores estables , que se obtienen utilizando un nombre en mayúscula o encerrando backticks.
Con One
y two
definidos en otro lugar, o pasados como parámetros de función:
val One: Int = 1
val two: Int = 2
Se pueden emparejar contra de la siguiente manera:
def g(x: Int): String = x match {
case One => "One"
case `two` => "Two"
}
A diferencia de otros lenguajes de programación como Java, por ejemplo, no hay caída. Si un bloque de caso coincide con una entrada, se ejecuta y se completa la coincidencia. Por lo tanto, el caso menos específico debería ser el último bloque de caso.
def f(x: Int): String = x match {
case _ => "Default"
case 1 => "One"
}
f(5) // "Default"
f(1) // "Default"
Coincidencia de patrones con identificador estable
En la coincidencia de patrón estándar, el identificador utilizado sombreará cualquier identificador en el ámbito de envolvente. A veces es necesario hacer coincidir en la variable del ámbito de cierre.
La siguiente función de ejemplo toma un carácter y una lista de tuplas y devuelve una nueva lista de tuplas. Si el carácter existía como el primer elemento en una de las tuplas, el segundo elemento se incrementa. Si aún no existe en la lista, se crea una nueva 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)
}
Lo anterior demuestra la coincidencia de patrones donde la entrada del método, char
, se mantiene 'estable' en la coincidencia de patrones: es decir, si llama a tabulate('x', ...)
, la primera declaración de caso se interpretaría como:
case('x', count) => ...
Scala interpretará cualquier variable demarcada con una marca de verificación como un identificador estable: también interpretará cualquier variable que comience con una letra mayúscula de la misma manera.
Patrón de coincidencia en una secuencia
Para verificar un número preciso de elementos en la colección
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!"
}
Para extraer el (los) primer (s) elemento (s) y guardar el resto como una colección:
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"
}
En general, cualquier forma que se pueda usar para construir una secuencia se puede usar para hacer una comparación de patrones con una secuencia existente.
Tenga en cuenta que mientras usa Nil
y ::
funcionará cuando el patrón coincida con una secuencia, la convierte a una List
y puede tener resultados inesperados. Limítate a Seq( ...)
y +:
para evitar esto.
Tenga en cuenta que mientras usa ::
no funcionará para WrappedArray
, Vector
, etc., vea:
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
Y 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
Guardias (si son expresiones)
Las declaraciones de casos se pueden combinar con las expresiones if para proporcionar lógica adicional cuando coinciden los patrones.
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"
}
}
Es importante asegurarse de que sus guardias no creen una coincidencia no exhaustiva (el compilador a menudo no lo detectará):
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case None => doSomethingIfNone
}
Esto lanza un MatchError
en los números impares. Debe tener en cuenta todos los casos, o usar un caso de coincidencia de comodín:
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case _ => doSomethingIfNoneOrOdd
}
Coincidencia de patrones con clases de casos
Cada clase de caso define un extractor que puede usarse para capturar a los miembros de la clase de caso cuando coincida el patrón:
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
}
Se aplican todas las reglas normales de coincidencia de patrones: puede usar protecciones y expresiones constantes para controlar la coincidencia:
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
}
Coincidencia en una opción
Si está emparejando en un tipo de opción :
def f(x: Option[Int]) = x match {
case Some(i) => doSomething(i)
case None => doSomethingIfNone
}
Esto es funcionalmente equivalente a usar fold
, o map
/ getOrElse
:
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
Rasgos sellados a juego del patrón
Cuando el patrón coincida con un objeto cuyo tipo es un rasgo sellado, Scala verificará en el momento de la compilación que todos los casos están "completamente emparejados":
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.
}
Si posteriormente se agrega una nueva case class
para Shape
, todas las declaraciones de match
en Shape
comenzarán a mostrar una advertencia del compilador. Esto facilita la refactorización completa: el compilador alertará al desarrollador de todo el código que debe actualizarse.
Coincidencia de patrones 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.")
}
En este ejemplo, la expresión regular intenta coincidir con la dirección de correo electrónico proporcionada. Si lo hace, se extrae e imprime el nombre de userName
y el domain
. topDomain
también se extrae, pero nada se hace con él en este ejemplo. Llamando .r
en una cadena str
es equivalente a la new Regex(str)
. La función r
está disponible a través de una conversión implícita .
Carpeta de patrones (@)
El signo @
vincula una variable a un nombre durante una coincidencia de patrón. La variable enlazada puede ser todo el objeto coincidente o parte del objeto coincidente:
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
El identificador enlazado se puede utilizar en filtros condicionales. Así:
case Circle(r) if r > 9 => s"large circle"
Se puede escribir como:
case c @ Circle(_) if c.radius > 9 => s"large circle"
El nombre se puede vincular solo a una parte del patrón coincidente:
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)
Tipos de coincidencia de patrones
La coincidencia de patrones también se puede usar para verificar el tipo de una instancia, en lugar de usar 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
El orden de los casos es 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
De esta manera, es similar a una declaración clásica de "cambio", sin la funcionalidad de acceso directo. Sin embargo, también puede establecer patrones de coincidencia y valores de 'extracción' del tipo en cuestión. Por ejemplo:
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?'
Tenga en cuenta que en el caso de Foo
y Woo
usamos el guión bajo ( _
) para "coincidir con una variable no vinculada". Es decir, el valor (en este caso Hadas
y 27
, respectivamente) no está vinculado a un nombre y, por lo tanto, no está disponible en el controlador para ese caso. Esta es una taquigrafía útil para hacer coincidir el valor 'cualquiera' sin preocuparse por cuál es ese valor.
Coincidencia de patrones compilada como conmutador de tablas o de búsqueda
La anotación @switch
le dice al compilador que la declaración de match
se puede reemplazar con una sola instrucción de tableswitch
en el nivel de bytecode. Esta es una optimización menor que puede eliminar comparaciones innecesarias y cargas variables durante el tiempo de ejecución.
La anotación @switch
funciona solo para coincidencias contra constantes literales e identificadores de final val
. Si la coincidencia de patrón no se puede compilar como un tableswitch
/ lookupswitch
, el compilador mostrará una advertencia.
import annotation.switch
def suffix(i: Int) = (i: @switch) match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
Los resultados son los mismos que en una coincidencia de patrón normal:
scala> suffix(2)
res1: String = "2nd"
scala> suffix(4)
res2: String = "4th"
De la Documentación Scala (2.8+) - @switch
:
Una anotación que se aplicará a una expresión de coincidencia. Si está presente, el compilador verificará que la coincidencia se haya compilado en un conmutador de tablas o un interruptor de búsqueda, y emitirá un error si en su lugar se compila en una serie de expresiones condicionales.
De la especificación de Java:
- tableswitch : "Accede a la tabla de saltos por índice y salta"
- interruptor de búsqueda : "Acceda a la tabla de saltos por coincidencia de teclas y salte"
Combinando múltiples patrones a la vez
El |
se puede usar para que una única sentencia de caso coincida con varias entradas para obtener el mismo resultado:
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.
Tenga en cuenta que si bien los valores coincidentes funcionan de esta manera, la siguiente coincidencia de tipos causará problemas:
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"
}
}
Si en el último caso (con _
) no necesita el valor de la variable no vinculada y solo quiere hacer otra cosa, está bien:
def matcher(g: FooBar):String = {
g match {
case Foo(_) | Bar(_) => "Is either Foo or Bar." // Works fine
case _ => "Could not match"
}
}
De lo contrario, te quedas con la división de sus casos:
def matcher(g: FooBar):String = {
g match {
case Foo(s) => s
case Bar(s) => s
case _ => "Could not match"
}
}
Coincidencia de patrones en tuplas
Dada la siguiente List
de tuplas:
val pastries = List(("Chocolate Cupcake", 2.50),
("Vanilla Cupcake", 2.25),
("Plain Muffin", 3.25))
La comparación de patrones se puede utilizar para manejar cada elemento de manera diferente:
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") } }
El primer caso muestra cómo hacer coincidir una cadena específica y obtener el precio correspondiente. El segundo caso muestra el uso de if y la extracción de la tupla para que coincida con los elementos de la tupla.