La coincidencia de patrones es una característica importante en el desarrollo de software program. Mientras que la coincidencia de patrones se aplica en varios lugares, su uso precise se limita a change case
bloques. Quiero comparar el poder de la coincidencia de patrones en un par de lenguajes de programación con los que estoy familiarizado en esta publicación.
Supongo que cada lector está familiarizado con el change case
Sintaxis heredada de C. En resumen:
- El
change
La cláusula hace referencia a una declaración de retorno de valor. - Cada
case
La cláusula establece otra declaración; Si el valor coincide con la declaración, ejecuta el bloque relacionado. case
Las cláusulas se evalúan en orden. La primera cláusula que coincide con su bloqueo.- .Cª,
case
Las cláusulas son caídas; necesitas explícitamentebreak
Para escapar delchange
de lo contrario, el siguientecase
se evalúa
La coincidencia de patrones de Java
Comenzaré con Java, ya que fue el primer lenguaje de programación que utilicé en un contexto profesional.
La coincidencia de patrones de Java ha evolucionado mucho en sus versiones. Aquí está la muestra “oficial” para la versión 23, ligeramente modificada. Outline varios Form
implementaciones y un static
método para evaluar su perímetro. Definitivamente es no Un buen ejemplo de programación orientada a objetos.
interface Form { }
document Rectangle(double size, double width) implements Form { }
document Circle(double radius) implements Form { }
public class Fundamental {
static double getPerimeter(Form s) throws IllegalArgumentException {
return change (s) { //1
case Rectangle r when r.size() == r.width() -> { //2
System.out.println("Sq. detected");
yield 4 * r.size();
}
case Rectangle r -> //3
2 * r.size() + 2 * r.width();
case Circle c -> //4
2 * c.radius() * Math.PI;
default -> //5
throw new IllegalArgumentException("Unrecognized form");
};
}
}
- Referencia al
Form
parámetro comos
- Evaluar si
s
es unRectangle
y si elRectangle
es un cuadrado - Evaluar si
s
es unRectangle
- Evaluar si
s
es unCircle
- Si ninguna de las cláusulas anteriores coincide, predeterminada para lanzar una excepción
Esta versión de Java será nuestra línea de base.
Características del nuevo change
Sintaxis
La nueva sintaxis tiene algunas ventajas sobre el Legacy No Arrow change
heredado de C.
Estilo C case
Las cláusulas son caídas. Una vez que el tiempo de ejecución ha ejecutado un case
bloque, ejecuta el siguiente case
bloquear. Para evitarlo, debe establecer un break
explícitamente. En los viejos tiempos no tan buenos, algunos desarrolladores aprovecharon esta función para evitar la duplicación de código.
Sin embargo, la experiencia ha demostrado que fue una gran causa de posibles errores: las prácticas de codificación modernas tienden a evitarlo. Sin embargo, es demasiado fácil olvidar un break
. Con la nueva sintaxis de flecha, el tiempo de ejecución solo evalúa el correspondiente case
bloquear.
Estilo C case
Las cláusulas solo evalúan valores simples. La comunidad celebró a Java 7 como una gran bendición cuando permitía valores de cadena. Java 21 fue aún más lejos: junto con la sintaxis de la flecha, permitió encender los tipos. En el ejemplo anterior, verificamos lo exacto Form
tipo.
Además, si enciende las clases y Si la jerarquía de clases es sealed
el compilador puede detectar automáticamente los casos faltantes. Finalmente, la versión 24 agregó la opcional adicional when
filtrar.
Por otro lado, en la antigua sintaxis C, el tiempo de ejecución salta directamente al correcto case
cláusula. La nueva sintaxis de flecha los evalúa secuencialmente, exactamente como si con if else
declaraciones.
En las siguientes secciones, portaremos el código a otros idiomas.
La coincidencia de patrones de Scala
La coincidencia de patrones de Scala ha sido insuperable desde su inicio. Kotlin se inspiró mucho.
trait Form
case class Rectangle(size: Double, width: Double) extends Form
case class Circle(radius: Double) extends Form
def getPerimeter(s: Form) = {
s match { //1
case Rectangle(size, width) if size == width => //2-3
println("Sq. detected")
4 * size
case Rectangle(size, width) => 2 * size + 2 * width //3-4
case Circle(radius) => 2 * radius * Math.PI //3-5
case _ => throw new IllegalArgumentException("Unrecognized form") //6
}
}
- Usar el
s
referencia directamente - Evaluar si
s
es unRectangle
y si elRectangle
es un cuadrado - Scala también coincide con los atributos de clase
- Evaluar si
s
es unRectangle
- Evaluar si
s
es unCircle
- Si ninguna de las cláusulas anteriores coincide, predeterminada para lanzar una excepción
La coincidencia de patrones de Kotlin
Traducir el código de Java a Kotlin. Por eso, nosotros debe activar el experimental Xwhen-guards
característica de compilación descrita en Maintain-371.
interface Form
information class Rectangle(val size: Double, val width: Double): Form
information class Circle(val radius: Double): Form
enjoyable getPerimeter(s: Form) = when (s) { //1
is Rectangle if s.size == s.width -> { //2
println("Sq. detected")
4 * s.size
}
is Rectangle -> 2 * s.size + 2 * s.width //3
is Circle -> 2 * s.radius * Math.PI //4
else -> throw IllegalArgumentException("Unknown form") //5
}
}
- Referencia al
Form
parámetro comos
- Evaluar si
s
es unRectangle
y si elRectangle
‘swidth
es igual a sutop
- Evaluar si
s
es unRectangle
- Evaluar si
s
es unCircle
- Si ninguna de las cláusulas anteriores coincide, predeterminada para lanzar una excepción
La coincidencia de patrones de Kotlin se parece mucho a Java, con ligeros cambios de sintaxis, p.ej, if
reemplazo when
.
La evolución comparativa de Kotlin vs. Java en la coincidencia de patrones es bastante esclarecedora. Java se quedó atrás de la sintaxis Legacy C, mientras que Kotlin ya podría coincidir con tipos de concreto desde el principio. En 2020, Java 14 redujo la brecha con el operador de flecha; Un año después, Java 16 cerró la brecha por completo con la capacidad de igualar en tipos de concreto. Finalmente, Java 23 agregó el when
cláusula.
La naturaleza experimental de Kotlin’s if
Significa que Java ha superado a Java, al menos en esta área! Es lo suficientemente raro como para ser anotado.
La coincidencia de patrones de Python
Antes, Python no ofreció nada related al change
Declaración de los idiomas JVM anteriores. De la versión 3.10, ofrece la misma capacidad de manera elegante:
class Form:
move
class Rectangle(Form):
def __init__(self, size: float, width: float):
self.size = size
self.width = width
class Circle(Form):
def __init__(self, radius: float):
self.radius = radius
def get_perimeter(s: Form) -> float:
match s:
case Rectangle(size=l, width=w) if l == w:
print("Sq. detected")
return 4 * l
case Rectangle(size=l, width=w):
return 2 * l + 2 * w
case Circle(radius=r):
return 2 * pi * r
case _:
elevate ValueError("Unknown form")
El tiempo de ejecución evalúa el case
Cláusulas secuencialmente, como en los idiomas JVM.
Mataje del patrón de Rust
El enfoque de Rust para la gestión de la memoria no juega bien con los tipos de verificación. En resumen, Rust ofrece dos conceptos base:
- Las estructuras son marcadores de posición de datos estructurados; Su tamaño de memoria se conoce en el momento de la compilación
- Los rasgos son contratos, similares a las interfaces
- Puede proporcionar una implementación de un rasgo para una estructura
- Hacer referencia a una variable por su rasgo tiene consecuencias. Dado que la referencia puede apuntar a estructuras de diferentes tamaños, el compilador no puede conocer su tamaño en el tiempo de compilación y debe colocar la variable en el montón en lugar de la pila.
Traté de transferir el código authentic para oxidar uno a uno con fines educativos. El Java authentic no aprovecha el polimorfismo; No es genial. En Rust, es aún más feo. Es interesante darse cuenta de que si bien Rust no es un lenguaje de programación orientado a objetos, te lleva a usar polimorfismo.
ADVERTENCIA: ¡No recomiendo el siguiente código y negaré ninguna asociación con él!
trait Form: Any {
fn as_any(&self) -> &dyn Any; //1
}
struct Circle {
radius: f64,
}
struct Rectangle {
width: f64,
top: f64,
}
impl Form for Circle {
fn as_any(&self) -> &dyn Any { //2
self
}
}
impl Form for Rectangle {
fn as_any(&self) -> &dyn Any { //2
self
}
}
fn get_perimeter(s: Field) -> f64 {
match s.as_any() { //3
any if any.is::() => { //4
let rectangle = any.downcast_ref::().unwrap(); //5
if rectangle.width == rectangle.top {
println!("Sq. matched");
4.0 * rectangle.width
} else {
2.0 * (rectangle.width + rectangle.top)
}
}
any if any.is::() => { //4
let circle = any.downcast_ref::().unwrap(); //5
2.0 * std::f64::consts::PI * circle.radius
}
_ => panic!()
}
}
- El sistema de tipos de Rust no ofrece una forma de obtener el tipo de variable. Debemos crear una función dedicada para eso.
- Implementar la función para estructuras
- Coincidir con el tipo de estructura subyacente
- Verifique el tipo
- Abatido a la estructura subyacente para usar sus campos
Este puerto de código synthetic por encima de las habilidades de coincidencia de patrones de Rust. Se aplica en muchos lugares.
Conclusión
Entre todos los idiomas descritos en la publicación, Scala fue la primera en proporcionar coincidencia de patrones en change
cláusulas. Durante muchos años, fue el Grial con el que otros intentaron ponerse al día; Kotlin y Java finalmente han llegado a esta etapa.
Fuera de la JVM, Python y Rust cuentan con potentes capacidades de coincidencia de patrones.
Con la destrucción, la coincidencia de patrones es una gran ayuda para los desarrolladores que desean escribir código legible y mantenible.
El código fuente completo para esta publicación se puede encontrar en GitHub^.
Para ir más allá:
Publicado originalmente en un geek de Java el 20 de julio de 2025