Translate

Mostrando las entradas para la consulta pattern matching ordenadas por relevancia. Ordenar por fecha Mostrar todas las entradas
Mostrando las entradas para la consulta pattern matching ordenadas por relevancia. Ordenar por fecha Mostrar todas las entradas

sábado, 11 de julio de 2026

Polimorfismo vs Pattern Matching


Durante los últimos años aparecieron innumerables artículos anunciando la muerte del polimorfismo. La llegada de sealed classes, union types y pattern matching hizo que muchos comenzaran a tratar la orientación a objetos como una técnica del pasado.

Sin embargo, las críticas al polimorfismo no son nuevas. Tampoco las críticas al pattern matching. De hecho, muchas de ellas fueron formuladas por las personas que diseñaron estos paradigmas mucho antes de que existieran Java, Kotlin o Rust.

Vale la pena volver a esas discusiones:


Agregar operaciones es difícil

Philip Wadler formuló en 1998 el famoso Expression Problem.

Su observación sigue siendo una de las críticas más fuertes al polimorfismo: los sistemas orientados a objetos hacen muy sencillo incorporar nuevos tipos, pero extremadamente costoso agregar nuevas operaciones.

Cada nueva operación obliga a modificar toda la jerarquía de clases.

Desde esta perspectiva, el pattern matching invierte completamente el problema: agregar una operación suele consistir simplemente en escribir una nueva función.

No es casualidad que muchos desarrolladores funcionales vean al patrón Visitor como una consecuencia directa de esta limitación.


El comportamiento queda disperso

Martin Odersky ha defendido repetidamente el pattern matching como una herramienta para expresar algoritmos completos en un único lugar.

Cuando la lógica está distribuida entre decenas de implementaciones distintas, comprender una operación completa implica recorrer buena parte del proyecto.

Con pattern matching, el algoritmo aparece concentrado y resulta mucho más sencillo razonar sobre él.

La pregunta implícita es incómoda para la orientación a objetos: ¿Queremos organizar el código alrededor de los datos o alrededor de las operaciones?


El despacho dinámico oculta demasiado

Otra crítica frecuente desde el mundo funcional es que el polimorfismo sacrifica legibilidad en favor de la extensibilidad.

Una llamada aparentemente inocente puede terminar ejecutando cualquiera de decenas de implementaciones.

El flujo del programa deja de ser evidente y pasa a depender de la jerarquía de tipos.

Para muchos, el pattern matching hace exactamente lo contrario: vuelve explícitas todas las alternativas posibles.


Pattern Matching rompe el encapsulamiento

Si hubiera que elegir una crítica histórica, probablemente sea la de Alan Kay.

Kay insistió durante décadas en que los objetos nunca fueron concebidos como estructuras de datos con comportamiento, sino como entidades que colaboran mediante el envío de mensajes.

Desde esa mirada, inspeccionar explícitamente el estado de un objeto para decidir qué hacer significa abandonar el modelo de objetos y volver a programar sobre representaciones internas.

No es una cuestión sintáctica.

Es un cambio de filosofía.


Cada nuevo tipo obliga a revisar todos los matches

Bertrand Meyer, creador de Eiffel y autor del principio Open/Closed, probablemente encontraría aquí su principal objeción.

Cuando aparece un nuevo subtipo, todos los matches relevantes deben ser revisados.

La lógica que antes estaba distribuida vuelve a concentrarse en múltiples lugares.

El costo del cambio deja de estar en agregar operaciones y pasa a estar en agregar variantes del modelo.

Exactamente el problema inverso al planteado por Wadler.


Un match de 500 líneas sigue siendo un switch de 500 líneas

Quizás sea la crítica más práctica.

El pattern matching no garantiza buen diseño.

Nada impide escribir enormes bloques de decisiones difíciles de mantener.

Cambiar la sintaxis de switch por match no elimina automáticamente el acoplamiento ni mejora la modularidad.

Muchas veces simplemente produce un switch más elegante.


Entonces... ¿quién tiene razón?

Probablemente ambos.

Philip Wadler demostró que la orientación a objetos favorece la extensión por tipos.

Bertrand Meyer mostró que esa misma decisión permite mantener el sistema cerrado frente a modificaciones cuando aparecen nuevos comportamientos internos.

Alan Kay defendió durante toda su carrera que los objetos deberían ocultar completamente su representación.

Martin Odersky respondió que muchas veces entender un algoritmo completo es más importante que esconder la representación de los datos.


No son respuestas contradictorias.

Responden preguntas diferentes.


Cada cierto tiempo la industria intenta declarar un ganador.

Hace veinte años parecía que todo debía resolverse con herencia y polimorfismo.

Hoy pareciera que todo debería resolverse con pattern matching.

La realidad es bastante menos interesante para quienes buscan una respuesta definitiva: ninguno reemplaza al otro.


Uno optimiza la evolución del sistema cuando aparecen nuevos tipos.

El otro optimiza la evolución cuando aparecen nuevas operaciones.


No existe una técnica superior.


Existe una pregunta mucho más importante:

¿Qué es lo que cambia con mayor frecuencia en tu dominio?


Porque esa respuesta, mucho más que el paradigma elegido, suele determinar cuál de las dos herramientas encaja mejor.

viernes, 4 de octubre de 2024

Pattern Matching en TypeScript


Pattern Matching es una característica poderosa que permite comparar una estructura de datos con un patrón y ejecutar el código dependiendo de cómo coincidan. Aunque TypeScript no tiene un soporte nativo de pattern matching al estilo de lenguajes como Scala o Haskell, pero se puede simular de manera efectiva utilizando algunas características como los tipos discriminados y el refinamiento de tipos para implementar pattern matching. Estos tipos combinan un campo común discriminante que puede diferenciar uniones de tipos de forma segura.

Veamos un ejemplo:


type Shape = 

  | { kind: 'circle', radius: number }

  | { kind: 'square', sideLength: number }

  | { kind: 'rectangle', width: number, height: number };


function area(shape: Shape): number {

  switch (shape.kind) {

    case 'circle':

      return Math.PI * shape.radius ** 2;

    case 'square':

      return shape.sideLength ** 2;

    case 'rectangle':

      return shape.width * shape.height;

  }

}


const myCircle: Shape = { kind: 'circle', radius: 5 };

console.log(area(myCircle)); // 78.53981633974483


Otra forma de hacer pattern matching es mediante guard clauses, que son condiciones específicas para cada caso. Aquí tienes un ejemplo:


function printNumber(x: number | string): string {

  if (typeof x === 'number') {

    return `Es un número: ${x}`;

  } else if (typeof x === 'string') {

    return `Es una cadena de texto: ${x}`;

  } else {

    return `Valor no soportado`;

  }

}


// Uso

console.log(printNumber(42));   // Es un número: 42

console.log(printNumber('42')); // Es una cadena de texto: 42


TypeScript también permite un estilo de pattern matching mediante desestructuración de objetos y arrays.


type Person = { name: string, age: number };

type Animal = { species: string };


function describe(input: Person | Animal): string {

  if ('name' in input) {

    return `Persona: ${input.name}, Edad: ${input.age}`;

  } else {

    return `Especie: ${input.species}`;

  }

}


// Uso

const person: Person = { name: 'John', age: 30 };

const animal: Animal = { species: 'Dog' };


console.log(describe(person)); // Persona: John, Edad: 30

console.log(describe(animal)); // Especie: Dog


El uso de `switch` puede complementarse con guardias para realizar un matching más fino de patrones, filtrando por condiciones adicionales.


function classifyNumber(x: number): string {

  switch (true) {

    case x < 0:

      return 'Número negativo';

    case x === 0:

      return 'Cero';

    case x > 0:

      return 'Número positivo';

    default:

      return 'Valor desconocido';

  }

}


console.log(classifyNumber(-5));  // Número negativo

console.log(classifyNumber(0));   // Cero

console.log(classifyNumber(10));  // Número positivo


Si bien TypeScript no tiene soporte nativo para el pattern matching al nivel de otros lenguajes funcionales, podemos simularlo utilizando sus características de refinamiento de tipos, tipos discriminados, guard clauses y desestructuración.

Con estos enfoques, puedes aplicar las ideas de pattern matching de forma clara y eficiente en TypeScript. Este tipo de técnica puede mejorar la legibilidad de tu código y hacerlo más fácil de mantener.


domingo, 13 de abril de 2014

Pattern matching


Pattern Matching nace del paradigma funcional aunque hoy en día lenguaje multiparadigma lo implementan como Scala o Kotlin. Pattern Matching permite definir funciones por medio de macheo de parámetros y resultados. Veamos un ejemplo en Haskell de definición de factorial:

 factorial 0 = 1
 factorial n = n * factorial (n - 1)

Para ver la diferencia entre la forma de resolver el problema sin Pattern Matching, voy a resolver lo mismo en C++

 int factorial(int n) {
       if (n == 0) {
              return 1;
       } else {
             return n * factorial(n-1);
       }
 }

Si comparamos nos ahorramos 5 lineas; pero el tema no es solo escribir menos código. Además debemos pensar: ¿Cuál les parece más legible? ¿Cuál es más simple?

Muchos lenguajes implementan Pattern Matching, al venir del paradigma funcional normalmente los lenguajes funcionales como Erlang lo implementan; vamos a hacer la función de factorial en Erlang.

 fac(0) -> 1;
 fac(N) when N > 0, is_integer(N) -> N * fac(N-1).

Se puede ver que en este caso se indica que debe ser positivo el número; si se ingresa un valor negativo lanza un error. Ojo estas restricciones también la podíamos hacer en Haskell.

 Factorial con ML se vería de la siguiente manera:

 fun fac 0 = 1
      | fac n = n * fac (n - 1)

 Otros lenguajes multiparadigma nos permiten jugar con Patter Matching como Scala:

 def fact(n: Int): Int = n match {
     case 0 => 1
     case n => n * fact(n - 1)
   }

En este caso se define fact que devolverá un entero, y que el parámetro n debe coincidir con 0 y devuelve 1 o con cualquier otro valor y devolverá n * fact(n - 1).

 Por ejemplo Kotlin permite hacer Pattern Maching por el tipo de la siguiente manera:

 when (x) {
   is Int -> print(x)
   is List<Int> -> print(x.sum())
   !is Number -> print("No es un número")
   else -> print("No puedo hacer nada")
 }

Para citar otro ejemplo, veamos desarrollar una función que indique el número que se encuentra en una posición indicada por parámetro de la secuencia de Fibonacci, y lo desarrollaremos con F# :

 let rec fib n =
     match n with
     | 0 -> 0
     | 1 -> 1
     | 2 -> 1
     | _ -> fib (n - 1) + fib (n - 2)

 Si lo comparamos con Mathematica:
 fib[0]:=0
 fib[1|2]:=1
 fib[n_]:= fib[n-1] + fib[n-2]

En Mathematica podemos utilizar el | para decir esto o aquello y el _ para indicar cualquier otro valor.

Pattern Matching es una técnica muy simple, pero muy potente. Por ejemplo necesitamos el primer elemento de una lista podemos definirla de la siguiente manera, vamos a implementarlo en Haskell:

primero :: [a] -> a
primero [] = error "¡No puedes utilizar la función primero con una lista vacía!"
primero (x:_) = x

Con (x:_) estamos diciendo que puede llegar una lista por parámetro que está conformada por un elemento y el resto. Implementemos la función cantidadDeElementos que nos indique cuantos elementos tiene una lista:

cantidadDeElementos :: (Num b) => [a] -> b
cantidadDeElementos [] = 0
cantidadDeElementos (_:xs) = 1 + cantidadDeElementos xs

Una desventaja del uso de Pattern Matching es que nuestro código se verá muy afectado ante un cambio en las estructuras. Pero la ventajas son muchísimas, para citar algunas: legibilidad de código, simplicidad, menos lineas de código, etc...

martes, 29 de julio de 2025

Comparando obj != null, obj is { } y obj is not null en C#


En C#, hay varias formas de verificar si una variable no es null. Con la introducción de pattern matching, surgieron nuevas opciones como obj is { } y obj is not null. Aunque parecen equivalentes, no son exactamente lo mismo y conviene entender cuándo usar cada uno.


obj != null


Es la forma clásica de comprobar si una referencia no es null. Evalúa directamente la igualdad.


if (obj != null)

{

    // obj no es null

}


Cuándo usarlo:

  • Para chequeos simples de null.
  • En código tradicional o cuando la variable no participa en pattern matching.


Pero esto, no hace type test ni pattern matching. Solo evalúa igualdad.


obj is not null

Usa pattern matching para comprobar si la variable no es null.


if (obj is not null)

{

    // obj no es null

}


Cuándo usarlo:

  • Cuando querés mantener consistencia con otros patrones (`is int`, `is string s`, etc.).
  • Útil en expresiones más complejas con pattern matching.
  • Funciona igual que obj != null, pero es más expresivo en escenarios donde se usan patrones.


obj is { }


Este patrón comprueba que obj no sea null y coincida con un patrón de objeto.

{ } significa un objeto con cualquier valor de propiedades.


if (obj is { })

{

    // obj no es null

}


Cuándo usarlo:

  • Cuando querés usar pattern matching y, además, extraer propiedades en la misma expresión.


Ejemplo:


if (obj is { Nombre: var nombre, Edad: > 18 })

{

    Console.WriteLine($"{nombre} es mayor de edad");

}


¿Cuál es más performante?

obj != null : más rápido, es una simple comparación de referencia.

obj is not null: ligeramente más lento, pero casi idéntico (usa pattern matching internamente).

obj is { } : un poco más costoso porque implica verificar coincidencia de patrón de objeto.


Pero en la práctica, la diferencia de rendimiento es mínima y solo importa en código crítico (hot paths).


También se puede usar el operador null-coalescing (??):


var valor = obj ?? new MiObjeto();


O el null-conditional (?.):


obj?.Metodo();


Pero ese tema es para otro post ... 


lunes, 15 de diciembre de 2025

Pattern Matching en C++26 — ¿Qué es y cómo cambiará tu código?

 


C++ ha sido históricamente rico en herramientas de selección de flujo (if, switch, visitantes sobre std::variant, etc.), pero carece de una estructura nativa y unificada de pattern matching como la que vemos en Rust, Haskell o Swift. C++26 apunta (a través de la propuesta P2688R5) a llenar ese vacío. 

En ciencias de la computación, pattern matching es una forma declarativa de comparar una estructura de datos con uno o más patrones y —si coincide— ejecutar código asociado, extraer valores de forma segura y reducir mucho el boilerplate de código de control. 


Si alguna vez usaste pattern matching en lenguajes funcionales o en Rust:


match value {

    Some(x) => println!("Tiene valor: {}", x),

    None    => println!("No tiene valor"),

}


C++ tiene herramientas poderosas (como std::variant + std::visit), pero:

  • No existe un constructo nativo para comparar y destructurar tipos de forma concisa.
  • El tradicional switch solo funciona con valores integrales y carece de destructuración o bindings.


Pattern matching permite cosas como:

  • Comprobar la forma estructural de un valor.
  • Extraer datos de una std::tuple, std::variant, o tipos compuestos.
  • Reducir código repetitivo en código de control complejo.


Esto hace que tu código sea más claro, seguro y mantenible. 

¿Pattern Matching estará en C++26? Todavía no es definitivo.

El estándar C++26 aún está en borrador y pattern matching aún está en discusión dentro del comité WG21. La propuesta principal —P2688R5— introduce un nuevo constructo match muy similar a lo que otros lenguajes usan. 

Algunos desarrolladores creen que puede entrar en C++26 si todo marcha rápido. 

Otros opinan que podría retrasarse hasta C++29 debido a temas de diseño/sintaxis. 


Desde la propuesta P2688, se perfila un enfoque parecido a:


if (expr match [0, let foo]) {

    // foo está ligado a algo útil aquí

}


expr match -> result_type {

    pattern1 => /* acción 1 */,

    pattern2 => /* acción 2 */,

    _        => /* caso por defecto */

}


Aquí, match intenta casar expr con cada patrón.

let introduce variable(s) extraídas.

La sintaxis con => recuerda a Rust/Haskell, pero adaptada a C++ con sus propias reglas. 


Esta sintaxis aún no es parte definitiva del estándar — puede cambiar hasta la ratificación final.


viernes, 26 de septiembre de 2025

Pattern Matching en Elm: Desestructurando Datos de Forma Segura


El pattern matching en Elm es una herramienta poderosa para trabajar con listas, tuplas, registros y tipos algebraicos.

Permite desestructurar datos y cubrir todos los casos posibles de manera clara y segura, gracias al compilador.

Pattern Matching con listas:


primerElemento : List Int -> String

primerElemento lista =

    case lista of

        [] ->

            "Lista vacía"

        x :: xs ->

            "El primer elemento es " ++ String.fromInt x


[] → lista vacía.

x :: xs → descompone la lista en el primer elemento x y el resto xs.


Ejemplo:


primerElemento []          -- "Lista vacía"

primerElemento [10,20,30]  -- "El primer elemento es 10"


Pattern Matching con tuplas


sumaTupla : (Int, Int) -> Int

sumaTupla tupla =

    case tupla of

        (a, b) ->

            a + b


sumaTupla (3, 4)   -- 7


También podés combinar:


describeTupla : (String, Int) -> String

describeTupla (nombre, edad) =

    nombre ++ " tiene " ++ String.fromInt edad ++ " años"


Pattern Matching con registros


type alias Persona =

    { nombre : String

    , edad : Int

    }


saludo : Persona -> String

saludo persona =

    case persona of

        { nombre, edad } ->

            "Hola " ++ nombre ++ ", tenés " ++ String.fromInt edad ++ " años"


Ejemplo:

saludo { nombre = "Ana", edad = 30 }

-- "Hola Ana, tenés 30 años"


Pattern Matching con tipos algebraicos


Elm brilla con sus tipos definidos por el usuario.


type Resultado

    = Exito String

    | Error String


procesar : Resultado -> String

procesar res =

    case res of

        Exito mensaje ->

            "Todo salió bien: " ++ mensaje

        Error mensaje ->

            "Ocurrió un error: " ++ mensaje


Ejemplo:


procesar (Exito "Archivo guardado")

-- "Todo salió bien: Archivo guardado"


procesar (Error "Permiso denegado")

-- "Ocurrió un error: Permiso denegado"


Algo clave en Elm: el compilador exige cubrir todos los casos posibles en un case.

Si olvidás uno, el código no compila → esto evita errores en tiempo de ejecución.


  • case ... of permite desestructurar listas, tuplas, registros y tipos algebraicos.
  • Es una forma segura y declarativa de trabajar con datos.
  • El compilador obliga a cubrir todos los casos, garantizando que no haya estados no contemplados.


 El pattern matching es uno de los pilares que hace a Elm un lenguaje expresivo y confiable.


miércoles, 29 de agosto de 2012

Pattern Matching


Se acuerdan cuando iban al jardín de infantes que había una actividad en la cual de una fila de patitos había que encontrar el que miraba en la dirección contraria. Bueno en programación Pattern Matching no tiene nada que ver con los patitos.

Pattern Matching nace del paradigma funcional aunque hoy en día lenguaje multiparadigma lo implementan como Scala. Pattern Matching permite definir funciones por medio de macheo de parámetros y resultados. Veamos un ejemplo en Haskell de definición de factorial:

factorial 0 = 1
factorial n = n * factorial (n - 1)

A primera vista parece recursividad, pero esto viene del paradigma funcional por lo tanto no existe secuencia. Para ver la diferencia entre la forma de resolver el problema voy a resolver lo mismo en C++

int factorial(int n) {
      if (n == 0) {
             return 1;
      } else {
            return factorial(n-1);
      }
}

Si comparamos nos ahorramos 5 lineas; pero el tema no es solo escribir menos; sino que cual les parece más legible? cual es más simple?

Muchos lenguajes implementan pattern matching, al venir del paradigma funcional normalmente los lenguajes funcionales como Erlang; vamos a hacer la función de factorial en Erlang.

fac(0) -> 1;
fac(N) when N > 0, is_integer(N) -> N * fac(N-1).

Se puede ver que en este caso se indica que debe ser positivo el numero; si se ingresa un valor negativo lanza un error. Ojo esta restricciones también la podíamos hacer en Haskell.

Factorial con ML:

fun fac 0 = 1
     | fac n = n * fac (n - 1)

Otros lenguajes multiparadigma nos permiten jugar con Patter Matching como Scala:

def fact(n: Int): Int = n match {
    case 0 => 1
    case n => n * fact(n - 1)
  }

En este caso se define fact que devolverá un entero, y que el parámetro n debe matchear con 0 y devuelve 1 o con cualquier otro valor y devolverá n * fact(n - 1).

Por ejemplo Kotlin permite hacer pattern Maching por el tipo de la siguiente manera:

when (x) {
  is Int -> print(x)
  is List<Int> -> print(x.sum())
  !is Number -> print("Not even a number")
  else -> print("can't do anything")
}

Donde x es una variable que llega de parámetro.

Por ejemplo con F# una función de figonacci nos queda de la siguiente manera:

let rec fib n =
    match n with
    | 0 -> 0
    | 1 -> 1
    | _ -> fib (n - 1) + fib (n - 2)

Si lo comparamos con Haskell:

fib[0|1]:=1
fib[n_]:= fib[n-1] + fib[n-2]

En Haskell podemos utilizar el | para decir esto o aquello y el _ para decir cualquier cosa que venga.

Las implementaciones son muchas pero la idea en general es la misma; Pattern Matching permite mayor legibilidad y comodidad a programar y va ganando terreno entre los lenguajes más modernos.



martes, 9 de marzo de 2021

Pattern matching en Python.


Antes de empezar, si no sabes que es pattern matching, lee este post : https://emanuelpeg.blogspot.com/2014/04/pattern-matching.html

El PEP 634 nos muestra como va a ser el pattern matching en Python 3.10. Veamos el ejemplo que es super claro : 


match subject:

    case <pattern_1>:

        <action_1>

    case <pattern_2>:

        <action_2>

    case <pattern_3>:

        <action_3>

    case _:

        <action_wildcard>


Muy parecido a scala pero en vez de => usa los 2 puntos. 

Para que quede aun más claro veamos un ejemplo de uso: 


def http_error(status):

    match status:

        case 400:

            return "Bad request"

        case 404:

            return "Not found"

        case 418:

            return "I'm a teapot"

        case _:

            return "Something's wrong with the Internet"


y también se va a poder enumerar opciones con el |  : 


case 401 | 403 | 404:

    return "Not allowed"


A la vez, igual que en scala se va poder hacer matching con el valor y estructura de los objetos : 


# point is an (x, y) tuple

match point:

    case (0, 0):

        print("Origin")

    case (0, y):

        print(f"Y={y}")

    case (x, 0):

        print(f"X={x}")

    case (x, y):

        print(f"X={x}, Y={y}")

    case _:

        raise ValueError("Not a point")


Y como este matching más complejo podemos utilizar mejor el wildcard, etc... pero este post no trata de hablar de toda la potencia de esta nueva característica sino de trasnmitir la noticia. 


Dejo link : 

https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching

lunes, 7 de octubre de 2024

Evolución de Pattern Matching en C#


El Pattern Matching es una poderosa característica de C# que permite realizar operaciones basadas en coincidencias de patrones. A medida que han pasado las versiones, esta funcionalidad se ha enriquecido significativamente. A continuación, se explica cómo ha evolucionado:

Type Pattern (C# 7.0)

Permite verificar y hacer un cast de un tipo directamente.


if (obj is int i)

{

    Console.WriteLine(i); // Desde C# 7.0

}


Switch con Pattern Matching (C# 7.0)

El switch puede realizar comparaciones de tipos.


switch (obj)

{

    case int i:

        Console.WriteLine(i);  // Desde C# 7.0

        break;

}


Property Pattern (C# 8.0)

Verifica propiedades de un objeto en un switch.


var person = new Person("John", 30);

var result = person switch

{

    { Name: "John", Age: 30 } => "Matched!", // Desde C# 8.0

    _ => "No Match"

};


Positional Pattern (C# 8.0)

Permite trabajar con tipos que tienen métodos `Deconstruct`.


public record Point(int X, int Y);


var point = new Point(1, 2);

string description = point switch

{

    (1, 2) => "Point at (1,2)",  // Desde C# 8.0

    _ => "Other point"

};


Logical Patterns (and, or, not) (C# 9.0)

Se pueden combinar patrones usando operadores lógicos.


int x = 5;

string result = x switch

{

    > 0 and < 10 => "Between 1 and 9",   // Desde C# 9.0

    _ => "Other"

};


Relational Pattern (C# 9.0)

Se usa para comparar valores numéricos directamente en los patrones.


int x = 20;

string result = x switch

{

    > 10 => "Greater than 10",  // Desde C# 9.0

    _ => "10 or less"

};



List Patterns (C# 11.0)

Trabaja con colecciones de una manera más directa.


int[] numbers = { 1, 2, 3 };

var result = numbers switch

{

    [1, 2, ..] => "Starts with 1, 2",  // Desde C# 11.0

    _ => "Other"

};


 Slice Patterns (C# 11.0)

Captura sublistas utilizando `..`.


int[] numbers = { 1, 2, 3, 4 };

var result = numbers switch

{

    [_, _, .. int[] rest] => $"Rest: {string.Join(", ", rest)}",  // Desde C# 11.0

    _ => "No Match"

};


Y falto hablar de las Guards. Un guard es una condición adicional que puedes usar dentro de un patrón de coincidencia para hacer una verificación más específica. Se utiliza con la palabra clave when. Veamos un ejemplo:


int number = 5;


string result = number switch

{

    int n when n > 0 => "Positive number",

    int n when n < 0 => "Negative number",

    _ => "Zero"

};


Console.WriteLine(result);  // Output: Positive number


El Pattern Matching ha evolucionado desde su introducción en C# 7.0, permitiendo escribir código más expresivo, con patrones que facilitan el trabajo con tipos, propiedades, listas y operadores lógicos. Las versiones más recientes, como C# 11.0, añaden aún más flexibilidad, como los List y Slice Patterns. ¡Con cada nueva versión, el Pattern Matching sigue siendo una de las características más poderosas del lenguaje!

martes, 5 de noviembre de 2024

Pattern Matching en Typescript con ts-pattern


En TypeScript, a menudo necesitamos simplificar la lógica de condiciones, y aunque existen alternativas como if-else o switch, estas pueden volverse confusas en estructuras más complejas. Aquí es donde ts-pattern entra en juego, ofreciendo una forma poderosa y funcional de hacer pattern matching. Inspirado en técnicas de lenguajes como Haskell y Scala, ts-pattern permite coincidir patrones de datos y manejar casos de manera clara y estructurada.

ts-pattern es una biblioteca que lleva el pattern matching a TypeScript, haciéndolo flexible y adecuado para manejar varios tipos de datos y patrones complejos. Esto ayuda a escribir código más conciso y fácil de mantener, especialmente en proyectos grandes donde las condiciones exhaustivas son comunes.

Para instalar ts-pattern en tu proyecto, simplemente ejecuta:


npm install ts-pattern



Luego, impórtalo en tu archivo TypeScript:


import { match } from 'ts-pattern';


Veamos un ejemplo básico para entender cómo funciona match en strings:


const saludo = match('hola')

  .with('hola', () => '¡Hola Mundo!')

  .with('adiós', () => '¡Hasta luego!')

  .otherwise(() => 'Desconocido');

console.log(saludo);  // Salida: ¡Hola Mundo!


ts-pattern también permite manejar objetos anidados o arrays, facilitando el trabajo con estructuras más detalladas. Supongamos que tenemos un estado de carga y queremos manejarlo de manera exhaustiva:


type Estado = 'cargando' | 'éxito' | 'error';


const mensaje = match<Estado>('éxito')

  .with('cargando', () => 'Cargando...')

  .with('éxito', () => '¡Datos cargados!')

  .with('error', () => 'Hubo un problema.')

  .exhaustive();

console.log(mensaje);  // Salida: ¡Datos cargados!


Con .exhaustive(), ts-pattern se asegura de que todos los posibles valores de `Estado` están cubiertos, ayudándote a evitar errores futuros.

ts-pattern simplifica el manejo de múltiples condiciones en TypeScript, mejorando la claridad y la mantenibilidad del código. 

Dejo link: https://github.com/gvergnaud/ts-pattern

jueves, 7 de abril de 2016

C# 7 Previewed

Si puse el titulo en ingles, queda mejor "Previewed" que "una ojeada", jeje

C# cada vez se parece más a scala y en esta version trae pattern matching, toma tu torta:

case int x when x > 0:
case int y:

También descomposición por pattern matching:

Una de las cosas buenas que tiene pattern matching, es que se pueden descomponer elementos para poder analizarlos. En la próxima versión de C# testing.

 if (person is Professor {Subject is var s, FirstName is "Scott"})

Que más tenemos? Bueno, tambien tenemos mejoras en el tipo tupas por ejemplo. Pero todos los ojos están puestos en pattern matching.

Dejo link: http://www.infoq.com/news/2016/04/CSharp-7

domingo, 14 de abril de 2024

Pattern Matching en Erlang parte 2


Iniciemos un nuevo módulo llamado funciones en el que escribiremos un montón de funciones para explorar Pattern Matching:


-module(functions).

-compile(export_all). %% replace with -export() later, for God's sake!


La primera función que escribiremos es head/1, que actúa exactamente como erlang:hd/1, que toma una lista como argumento y devuelve su primer elemento. Se hará con la ayuda del operador | :


head([H|_]) -> H.


Para obtener el segundo elemento de una lista crearíamos la función:


second([_,X|_]) -> X.


Erlang simplemente deconstruirá la lista con Pattern Matching

1> c(functions).

{ok, functions}

2> functions:head([1,2,3,4]).

1

3> functions:second([1,2,3,4]).

2


Crearemos una función same/2 que toma dos argumentos y dice si son idénticos:


same(X,X) ->

true;

same(_,_) ->

false.


Y es así de simple. Antes de explicar cómo funciona la función, repasaremos nuevamente el concepto de variables vinculadas y no vinculadas, por si acaso.

Las variables independientes son variables sin ningún valor asociado. Vincular una variable es simplemente adjuntar un valor a una variable independiente. En el caso de Erlang, cuando se desea asignar un valor a una variable que ya está vinculada, se produce un error a menos que el nuevo valor sea el mismo que el anterior. 

Volviendo a nuestro código: lo que sucede cuando llamas a Same(a,a) es que la primera X se ve como independiente: automáticamente toma el valor a. Luego, cuando Erlang pasa al segundo argumento, ve que X ya está vinculado. Luego lo compara con el a pasado como segundo argumento y mira para ver si coincide. La coincidencia de patrones se realiza correctamente y la función devuelve verdadero. Si los dos valores no son iguales, esto fallará y pasará a la segunda cláusula de función, a la que no le importan sus argumentos (cuando eres el último en elegir, ¡no puedes ser exigente!) y en su lugar lo hará. falso retorno. Tenga en cuenta que esta función puede aceptar cualquier tipo de argumento. Funciona para cualquier tipo de datos, no solo listas o variables individuales. Como ejemplo bastante avanzado, la siguiente función imprime una fecha, pero sólo si tiene el formato correcto:


valid_time({Date = {Y,M,D}, Time = {H,Min,S}}) ->

    io:format("The Date tuple (~p) says today is: ~p/~p/~p,~n",[Date,Y,M,D]),

    io:format("The time tuple (~p) indicates: ~p:~p:~p.~n", [Time,H,Min,S]);

valid_time(_) ->

   io:format("Stop feeding me wrong data!~n").


Tenga en cuenta que es posible utilizar el operador = en el encabezado de la función, lo que nos permite hacer coincidir tanto el contenido dentro de una tupla ({Y,M,D}) como la tupla en su conjunto. La función se puede probar de la siguiente manera:

4> c(functions).

{ok, functions}

5> functions:valid_time({{2011,09,06},{09,04,43}}).

The Date tuple ({2011,9,6}) says today is: 2011/9/6,

The time tuple ({9,4,43}) indicates: 9:4:43.

ok

6> functions:valid_time({{2011,09,06},{09,04}}).

Stop feeding me wrong data!

ok


¡Sin embargo hay un problema! Esta función podría tomar cualquier valor como valor, incluso texto o átomos, siempre que las tuplas tengan la forma {{A,B,C}, {D,E,F}}. Esto denota uno de los límites de Pattern Matching: puede especificar valores realmente precisos, como un número conocido de átomos, o valores abstractos, como el head o tail de una lista, una tupla de N elementos o cualquier cosa _ , etc. Para solucionar este problema utilizamos Guards.


jueves, 20 de junio de 2019

C# 8 mejoras en Pattern Matching


Con C# 7 tuvimos Pattern Matching y con C# 8 tenemos mejoras!!!

Todo el mundo esta hablando de C# 8 y porque va salir C# 8, pues claro! Entre otras mejoras, han mejorado el Pattern Matching:

Ajuste posicional
En C# 7 hacíamos :

case Rectangle r when r.Length == 10 && r.Width == 10: return "Found 10x10 rectangle"

Y ahora :

case Rectangle (10, 10): return "Found 10x10 rectangle";

Propiedades
Con ajuste posicional el pattern matching es conciso, pero solo funciona si tiene un método Deconstruct adecuado. Cuando no lo tienes puedes usar un patrón de propiedad en su lugar.

case Rectangle {Width : 10 }: return "Found a rectangle with a width of 10"

Mejoras del deconstructor
Otra idea que se está considerando en el tema Abrir LDM en el ticket de coincidencia de patrones es permitir múltiples métodos Deconstruct con el mismo número de parámetros. Además de tener diferentes tipos, los parámetros deben tener un nombre diferente.

ITuple
La interfaz de ITuple, introducida en .NET 4.7.1 y .NET Core 2.0, plantea varias preguntas en C# 8. La idea básica es que si un objeto implementa esta interfaz, entonces puede participar en la comparación de patrones. Se están considerando tres escenarios con respecto a cuándo entrará en vigencia.

if (x is ITuple(3, 4)) // (1) permitted?
if (x is object(3, 4)) // (2) permitted?
if (x is SomeTypeThatImplementsITuple(3, 4)) // (3) permitted?

Una pregunta relacionada es si una clase implementa ITuple y hay un método de extensión Deconstruct, ¿cuál tiene prioridad? Idealmente, devolverían los mismos valores, pero se necesita un desempate si ese no es el caso.

domingo, 27 de agosto de 2017

Empezando con Elixir 6


Funciones

En Elixir y muchos lenguajes funcionales, las funciones son ciudadanos de primera clase.

Funciones Anónimas

Tal como su nombre lo indica, una función anónima no tiene nombre. Como vimos en la lección de Enum, éstos se pasan con frecuencia a otras funciones. Para definir una función anónima en Elixir necesitamos las palabras clave fn y end. Dentro de éstos podemos definir cualquier número de parámetros y cuerpos funcionales separados por ->

iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5

El operador &

El uso de funciones anónimas es una práctica común en Elixir hay una forma abreviada de hacerlo:

iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5

Como probablemente ya se supone, en la versión abreviada nuestros parámetros están disponibles para nosotros como & 1, & 2, & 3, y así sucesivamente.

Pattern Matching

Pattern matching no esta limitado solo a variables sino que tambien se pueden utilizar en funciones.

Se pueden utilizar pattern matching con el primer conjunto de parámetros que coinciden e invoca el cuerpo correspondiente:

iex> handle_result = fn
...>   {:ok, result} -> IO.puts "Handling result..."
...>   {:error} -> IO.puts "An error has occurred!"
...> end

iex> some_result = 1
iex> handle_result.({:ok, some_result})
Handling result...

iex> handle_result.({:error})

An error has occurred!

Funciones con nombre

Podemos definir funciones con nombre utilizando la palabra clave def. Las funciones definidas dentro de un modulo pueden ser accedidas por otros módulos:

defmodule Greeter do
  def hello(name) do
    "Hello, " <> name
  end
end

iex> Greeter.hello("Sean")
"Hello, Sean"

Si queremos definir una función en una linea podemos usar do:

defmodule Greeter do
  def hello(name), do: "Hello, " <> name
end

Y podemos utilizar pattern matching:

defmodule Length do
  def of([]), do: 0
  def of([_ | tail]), do: 1 + of(tail)
end

iex> Length.of []
0
iex> Length.of [1, 2, 3]
3

Funciones con nombre y número de argumentos

Como el lenguaje es de tipado débil, la firma de la función esta dada por el nombre de la función y la cantidad de parámetros. Es decir las funciones aceptan sobre carga y esta dada por el numero de parametros:

defmodule Greeter2 do
  def hello(), do: "Hello, anonymous person!"   # hello/0
  def hello(name), do: "Hello, " <> name        # hello/1
  def hello(name1, name2), do: "Hello, #{name1} and #{name2}"
                                                # hello/2

end

A diferencia de otros lenguajes, la firma de una función esta dada por el nombre y el numero de parámetros.

Funciones privadas

Una función privada solo se puede llamar dentro del modulo y se define con  defp :

defmodule Greeter do
  def hello(name), do: phrase <> name
  defp phrase, do: "Hello, "
end

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.phrase
** (UndefinedFunctionError) undefined function: Greeter.phrase/0
    Greeter.phrase()

Guards

En el ejemplo siguiente tenemos dos funciones con la misma firma, nos basamos en guards para determinar qué utilizar en función del tipo del argumento:

defmodule Greeter do
  def hello(names) when is_list(names) do
    names
    |> Enum.join(", ")
    |> hello
  end

  def hello(name) when is_binary(name) do
    phrase() <> name
  end

  defp phrase, do: "Hello, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

Argumentos por defecto

Se pueden utilizar argumentos por defecto:

defmodule Greeter do
  def hello(name, language_code \\ "en") do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello("Sean", "en")
"Hello, Sean"

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.hello("Sean", "es")
"Hola, Sean"

Cuando combinamos nuestro ejemplo de guard con argumentos predeterminados, nos topamos con un problema. Veamos cómo podría verse:

defmodule Greeter do
  def hello(names, language_code \\ "en") when is_list(names) do
    names
    |> Enum.join(", ")
    |> hello(language_code)
  end

  def hello(name, language_code \\ "en") when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

** (CompileError) def hello/2 has default values and multiple clauses, define a function head with the defaults

Elixir no le gusta los argumentos por defecto en múltiples funciones de coincidencia, puede ser confuso. Para manejar esto agregamos una cabeza de función con nuestros argumentos por defecto:

defmodule Greeter do
  def hello(names, language_code \\ "en")
  def hello(names, language_code) when is_list(names) do
    names
    |> Enum.join(", ")
    |> hello(language_code)
  end

  def hello(name, language_code) when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"

Dejo link: https://elixirschool.com/en/lessons/basics/functions/

jueves, 11 de julio de 2024

Como podemos manejar las referencias nulas?


El error más frecuente en Java es NullPointerException y me imagino que en otros lenguajes alguno similar...  Para abordar esto, se han introducido estructuras y operadores que ayudan a manejar la ausencia de valores de manera más segura y explícita. 

Por ejemplo en Java se introdujo la clase `Optional` en la versión 8 para manejar valores potencialmente nulos de una manera más segura. `Optional` es un contenedor que puede o no contener un valor no nulo.

import java.util.Optional;


public class OptionalExample {

    public static void main(String[] args) {

        Optional<String> optional = Optional.of("Hello, World!");

        

        // Verificar si hay un valor presente

        if (optional.isPresent()) {

            System.out.println(optional.get());

        }

        

        // Uso del método ifPresent

        optional.ifPresent(System.out::println);

        

        // Proveer un valor predeterminado

        String value = optional.orElse("Default Value");

        System.out.println(value);

        

        // Proveer un valor predeterminado usando un Supplier

        value = optional.orElseGet(() -> "Default Value from Supplier");

        System.out.println(value);

    }

}


Scala utiliza la clase `Option` para representar un valor opcional. `Option` tiene dos subclases: `Some` y `None`, lo que proporciona una forma elegante y funcional de manejar valores que pueden estar ausentes. Esta idea es similar a la monada `Maybe` en Haskell.


object OptionExample extends App {

  val someValue: Option[String] = Some("Hello, World!")

  val noneValue: Option[String] = None


  // Uso de getOrElse

  println(someValue.getOrElse("Default Value"))

  println(noneValue.getOrElse("Default Value"))


  // Uso del patrón de coincidencia (Pattern Matching)

  someValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }


  noneValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }

}


Scala "copio" esta forma de Haskell. Haskell utiliza el tipo de datos `Maybe` para manejar valores opcionales `Maybe` puede ser `Just` un valor o `Nothing`.


main :: IO ()

main = do

    let someValue = Just "Hello, World!"

    let noneValue = Nothing


    -- Uso de fromMaybe

    putStrLn (fromMaybe "Default Value" someValue)

    putStrLn (fromMaybe "Default Value" noneValue)


    -- Uso del patrón de coincidencia (Pattern Matching)

    case someValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


    case noneValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


Kotlin es similar a Scala en muchos aspectos pero no en este. Kotlin introduce el operador `?` para facilitar la gestión de valores nulos. Este operador se utiliza para declarar tipos de datos que pueden ser nulos y para realizar operaciones seguras contra nulos.


fun main() {

    var nullableString: String? = "Hello, World!"


    // Uso del operador ?. para llamadas seguras

    println(nullableString?.length)


    // Uso del operador ?: para proporcionar un valor predeterminado

    val length = nullableString?.length ?: 0

    println(length)


    nullableString = null


    // Uso de let para ejecutar código solo si el valor no es nulo

    nullableString?.let {

        println(it)

    }

}


C# ha incluido varias características para manejar valores nulos, como el operador `?`, que facilita el manejo seguro de tipos que pueden ser nulos.


using System;


class Program

{

    static void Main()

    {

        string? nullableString = "Hello, World!";

        

        // Uso del operador ?. para llamadas seguras

        Console.WriteLine(nullableString?.Length);


        // Uso del operador ?? para proporcionar un valor predeterminado

        int length = nullableString?.Length ?? 0;

        Console.WriteLine(length);


        nullableString = null;


        // Uso de pattern matching para verificar nulos

        if (nullableString is string nonNullString)

        {

            Console.WriteLine(nonNullString);

        }

    }

}


Rust maneja la ausencia de valores y los errores de una manera robusta utilizando los tipos `Option` y `Result`. `Option` puede ser `Some` o `None`, mientras que `Result` puede ser `Ok` o `Err`.


fn main() {

    let some_value: Option<String> = Some("Hello, World!".to_string());

    let none_value: Option<String> = None;


    // Uso de unwrap_or

    println!("{}", some_value.unwrap_or("Default Value".to_string()));

    println!("{}", none_value.unwrap_or("Default Value".to_string()));


    // Uso del patrón de coincidencia (Pattern Matching)

    match some_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }


    match none_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }

}


Go no tiene un tipo de datos específico para manejar valores opcionales, pero utiliza la convención de retornar múltiples valores, incluyendo un valor y un `error`. Que la verdad no me gusta, te pasas preguntando todo el tiempo si hay error o si los valores son nulos. 


package main


import (

    "errors"

    "fmt"

)


func getValue() (string, error) {

    return "Hello, World!", nil

}


func getNullableValue() (string, error) {

    return "", errors.New("no value")

}


func main() {

    value, err := getValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", value)

    }


    nullableValue, err := getNullableValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", nullableValue)

    }

}


Python utiliza la palabra clave `None` para representar la ausencia de valor. Aunque no tiene una estructura específica como `Optional`, los desarrolladores pueden utilizar condicionales y manejo de excepciones.


def get_value():

    return "Hello, World!"


def get_nullable_value():

    return None


value = get_value()

nullable_value = get_nullable_value()


if value is not None:

    print(value)

else:

    print("Default Value")


if nullable_value is not None:

    print(nullable_value)

else:

    print("Default Value")


Ruby utiliza `nil` para representar la ausencia de valor. Al igual que en Python, no tiene una estructura específica para valores opcionales, pero proporciona métodos para manejar `nil`.


value = "Hello, World!"

nullable_value = nil


# Uso del operador ||

puts value || "Default Value"

puts nullable_value || "Default Value"


# Uso de condicionales

puts value.nil? ? "Default Value" : value

puts nullable_value.nil? ? "Default Value" : nullable_value


C++ utiliza punteros inteligentes (`smart pointers`) para gestionar la memoria y prevenir errores relacionados con punteros nulos. Los punteros inteligentes, como `std::unique_ptr` y `std::shared_ptr`, se encargan de la gestión automática de la memoria.


#include <iostream>

#include <memory>


int main() {

    std::unique_ptr<int> uniquePtr(new int(42));

    if (uniquePtr) {

        std::cout << *uniquePtr << std::endl;

    }


    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

    if (sharedPtr) {

        std::cout << *sharedPtr << std::endl;

    }


    // Uso de weak_ptr para evitar ciclos de referencia

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {

        std::cout << *lockedPtr << std::endl;

    }


    return 0;

}


TypeScript, un superconjunto de JavaScript, permite tipos opcionales y tiene un soporte robusto para manejar valores `null` y `undefined`.


let nullableString: string | null = "Hello, World!";


// Uso del operador ? para llamadas seguras

console.log(nullableString?.length ?? 0);


// Uso de if para asegurar valores no nulos

if (nullableString !== null) {

    console.log(nullableString);

}


TypeScript utiliza tipos opcionales para manejar valores que pueden ser `null` o `undefined`, proporcionando un enfoque seguro para evitar errores comunes relacionados con valores nulos. El operador `?.` permite realizar llamadas seguras, y el operador `??` proporciona valores predeterminados en caso de valores `null` o `undefined`.

En fin, aunque la gestión de valores nulos varía entre lenguajes, la idea subyacente es la misma: proporcionar mecanismos más seguros y expresivos para manejar la ausencia de valores. Ya sea mediante clases contenedoras como `Optional` en Java y `Option` en Scala, tipos de datos como `Maybe` en Haskell, operadores específicos como `?` en Kotlin y C#, punteros inteligentes en C++, o enfoques específicos en Rust, Go, Python y Ruby, estos enfoques ayudan a reducir los errores y a escribir un código más robusto y mantenible.


miércoles, 10 de abril de 2024

Pattern Matching en Erlang


La primera función que escribiremos deberá saludar a alguien de manera diferente según el género. En la mayoría de los lenguajes necesitarías escribir algo similar a esto:

function greet(Gender,Name)

if Gender == male then

    print("Hello, Mr. %s!", Name)

else if Gender == female then

    print("Hello, Mrs. %s!", Name)

else

    print("Hello, %s!", Name)

end


Con Pattern Matching, Erlang le ahorra una gran cantidad de código repetitivo. Una función similar en Erlang se vería así:


greet(male, Name) ->

    io:format("Hello, Mr. ~s!", [Name]);

greet(female, Name) ->

    io:format("Hello, Mrs. ~s!", [Name]);

greet(_, Name) ->

    io:format("Hello, ~s!", [Name]).


La principal diferencia aquí es que usamos Pattern Matching para definir qué partes de una función deben usarse y vincular los valores que necesitamos al mismo tiempo. ¡No era necesario vincular primero los valores y luego compararlos! Entonces en lugar de:

function(X) ->

    Expression;

function(Y) ->

    Expression;

function(_) ->

    Expression.


para obtener resultados similares, pero en un estilo mucho más declarativo. Cada una de estas declaraciones de funciones se denomina cláusula de función. Las cláusulas de función deben estar separadas por punto y coma (;) y juntas forman una declaración de función. Una declaración de función cuenta como una declaración más grande y es por eso que la cláusula de función final termina con un punto. 


domingo, 17 de agosto de 2014

Sale una orden de Pattern Matching para C# y VB.net

No me canso de repetirlo la programación funcional cada vez esta ganando más espacio. Hoy quiero compartir la noticia que leí en infoQ.

Al parecer la versión 6 de C# y la 12 de VB.net van a incorporar pattern matching. Personalmente me parece bastante fea la implementación pero es mi opinión.

Veamos un ejemplo:

switch (e) {
  case X(): return Const(1);
  case Const(*): return Const(0);
  case Add(var Left, var Right): return Add(Deriv(Left), Deriv(Right));
  case Mult(var Left, var Right): return Add(Mult(Deriv(Left), Right), Mult(Left, Deriv(Right)));
  case Neg(var Value): return Neg(Deriv(Value));
}

Dejo link: http://www.infoq.com/news/2014/08/Pattern-Matching

viernes, 14 de agosto de 2020

Records y Pattern Matching para instanceof en JDK 16


Todavia no me aprendo java 11 y ya tenemos noticias de java 16. Al parecer viene con registros, cosa que particularmente no me gusta y no entiendo la necesidad. Un ejemplo sería : 

record Name(String firstName, String lastName){}

Los registros se pueden declarar en un archivo separado o como parte de otra clase. 

Cuando se define una clase normal, implícitamente se agrega un constructor predeterminado. Los registros tienen un comportamiento similar y contienen implícitamente varios miembros:

  • Campos privados para todos los componentes del registro.
  • Métodos de acceso público para todos los componentes del registro.
  • Constructor con argumentos para todos los componentes del registro. Este constructor se denomina constructor canónico.
  • equals, toString y hashCode con su implementación.

El registro definido anteriormente no contiene ningún código personalizado, pero es posible definir métodos explícitos. Incluso es posible declarar explícitamente los diversos métodos agregados implícitamente y el constructor canónico. Por ejemplo, es posible crear un método toString personalizado.

En JDK 14, se requería que el constructor canónico fuera público. Desde JDK 15 en adelante, el constructor canónico implícito obtiene el mismo modificador de acceso que la clase de registro. Al definir un constructor canónico explícito, el modificador de acceso debe proporcionar al menos tanto acceso como la clase de registro.

Como parte del cambio, también se modificó la anotación @Override. Ahora se puede utilizar para anotar un método de acceso explícito en un registro.

También vamos a tener pattern matching para el instaceof. Ahora debemos hacer : 

if (person instanceof Customer) {

    Customer customer = (Customer) person;

    customer.pay();

}

y con pattern matching vamos poder hacer : 

if (person instanceof Customer customer) {

    customer.pay();

}

y podemos utilizarlo en el equals por ejemplo: 

@Override

public boolean equals(Object o) {

    return o instanceof Customer other &&

            name.equals(other.name);

}

Dejo link: https://www.infoq.com/news/2020/08/java16-records-instanceof/


martes, 6 de junio de 2017

Empezando con Elixir 4

Pattern Matching

Pattern Matching es una parte importante y poderosa de elixir. Esto permite machear valores simples, estructura de datos e incluso funciones. Veamos como trabaja:

El operador Match
En elixir el operador = es un operador de macheo. Elixir espera que coincida los valores de la mano izquierda con los valores de la mano derecha. Si coinciden, devuelve el valor de la ecuación. De lo contrario, se produce un error. Vamos a ver un ejemplo:

iex> x = 1
1

Ahora vamos a probar el macheo:

iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1

Ahora veamos ejemplos con varias estructuras:

# Lists
iex> list = [1, 2, 3]
iex> [1, 2, 3] = list
[1, 2, 3]
iex> [] = list
** (MatchError) no match of right hand side value: [1, 2, 3]

iex> [1 | tail] = list
[1, 2, 3]
iex> tail
[2, 3]
iex> [2 | _] = list
** (MatchError) no match of right hand side value: [1, 2, 3]

# Tuples
iex> {:ok, value} = {:ok, "Successful!"}
{:ok, "Successful!"}
iex> value
"Successful!"
iex> {:ok, value} = {:error}
** (MatchError) no match of right hand side value: {:error}

Operador Pin

El operador de = realiza la asignación cuando el lado izquierdo de la coincidencia incluye una variable. En algunos casos, este comportamiento no es deseable. Para estas situaciones tenemos el operador pin: ^.

Veamos un ejemplo:

iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {x, ^x} = {2, 1}
{2, 1}
iex> x
2


iex> key = "hello"
"hello"
iex> %{^key => value} = %{"hello" => "world"}
%{"hello" => "world"}
iex> value
"world"
iex> %{^key => value} = %{:hello => "world"}
** (MatchError) no match of right hand side value: %{hello: "world"}

En Elixir 1.2 se agrega soporte para que el pin funcione en mapas o funciones.

iex> key = "hello"
"hello"
iex> %{^key => value} = %{"hello" => "world"}
%{"hello" => "world"}
iex> value
"world"
iex> %{^key => value} = %{:hello => "world"}
** (MatchError) no match of right hand side value: %{hello: "world"}

Veamos un ejemplo con funciones:

iex> greeting = "Hello"
"Hello"
iex> greet = fn
...>   (^greeting, name) -> "Hi #{name}"
...>   (greeting, name) -> "#{greeting}, #{name}"
...> end
#Function<12.54118792/2 in :erl_eval.expr/5>
iex> greet.("Hello", "Sean")
"Hi Sean"
iex> greet.("Mornin'", "Sean")
"Mornin', Sean"

Dejo link: https://elixirschool.com/lessons/basics/pattern-matching/

sábado, 15 de marzo de 2025

Clases Selladas y coincidencia de patrones en Java


Con la evolución de Java, dos características han tomado mayor protagonismo: las clases selladas (sealed classes) y el coincidencia de patrones (pattern matching). La combinación de ambas permite escribir código más seguro, expresivo y mantenible.

Las clases selladas permiten restringir qué clases pueden extender o implementar una clase o interfaz. Se declaran con la palabra clave sealed y deben especificar sus subclases con permits.


Por ejemplo:


public sealed class Figura permits Circulo, Poligono {}

public final class Circulo extends Figura {}

public sealed class Poligono extends Figura permits Triangulo {}

public non-sealed class Triangulo extends Poligono {}


Aquí, Figura solo puede ser extendida por Circulo y Poligono, mientras que Triangulo puede ser extendido libremente.

El pattern matching simplifica la lógica condicional al combinar la comprobación de tipos y la conversión en una sola operación.

Por ejemplo:


Object obj = "Hola, mundo!";

if (obj instanceof String s) {

    System.out.println(s.toUpperCase());

}


Si obj es un String, se asigna automáticamente a s, evitando el casting manual.


Tambien lo podemos usar con switch: 


static void procesar(Object obj) {

    switch (obj) {

        case String s -> System.out.println("Es una cadena: " + s.toUpperCase());

        case Integer i -> System.out.println("Es un número: " + (i * 2));

        default -> System.out.println("Tipo no soportado");

    }

}


El switch maneja diferentes tipos de objetos de manera más clara y concisa.

La combinación de ambas características permite que el compilador conozca todas las posibles subclases en tiempo de compilación, facilitando el uso de switch y garantizando que se manejen todos los casos.

Por ejemplo: 


public sealed interface Operacion permits Suma, Resta, Multiplicacion {}


public record Suma(int a, int b) implements Operacion {}

public record Resta(int a, int b) implements Operacion {}

public record Multiplicacion(int a, int b) implements Operacion {}


public int evaluar(Operacion op) {

    return switch (op) {

        case Suma s -> s.a() + s.b();

        case Resta r -> r.a() - r.b();

        case Multiplicacion m -> m.a() * m.b();

    };

}


Aquí, el switch cubre todas las implementaciones posibles de Operacion, asegurando que no haya casos no manejados.

Las clases selladas y el pattern matching en Java permiten escribir código más seguro y expresivo. Al proporcionar control sobre la herencia y simplificar la lógica condicional, facilitan la creación de software más mantenible y alineado con las tendencias modernas del desarrollo en Java.