Home Ciencia y Tecnología Comparación de combate de patrones en diferentes idiomas: Java, Scala y más

Comparación de combate de patrones en diferentes idiomas: Java, Scala y más

33
0

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ícitamente break Para escapar del changede lo contrario, el siguiente case 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");
        };
    }
}
  1. Referencia al Form parámetro como s
  2. Evaluar si s es un Rectangle y si el Rectangle es un cuadrado
  3. Evaluar si s es un Rectangle
  4. Evaluar si s es un Circle
  5. 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 sealedel 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
  }
}
  1. Usar el s referencia directamente
  2. Evaluar si s es un Rectangle y si el Rectangle es un cuadrado
  3. Scala también coincide con los atributos de clase
  4. Evaluar si s es un Rectangle
  5. Evaluar si s es un Circle
  6. 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
    }
}
  1. Referencia al Form parámetro como s
  2. Evaluar si s es un Rectangle y si el Rectangle‘s width es igual a su top
  3. Evaluar si s es un Rectangle
  4. Evaluar si s es un Circle
  5. 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!()
    }
}
  1. El sistema de tipos de Rust no ofrece una forma de obtener el tipo de variable. Debemos crear una función dedicada para eso.
  2. Implementar la función para estructuras
  3. Coincidir con el tipo de estructura subyacente
  4. Verifique el tipo
  5. 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

fuente