| 
 | 1 | +=== Pattern Matching  | 
 | 2 | + | 
 | 3 | +SScala tiene coincidencia de patrones (__pattern matching__) nativa, una de las ventajas sobre el Java __**puro**__. The basic syntax is close to Java'sLa sintaxis básica es similar al `switch` de Java:  | 
 | 4 | + | 
 | 5 | +[source,java]  | 
 | 6 | +----  | 
 | 7 | +val s = i match {    | 
 | 8 | +  case 1 => "one"  | 
 | 9 | +  case 2 => "two"  | 
 | 10 | +  case _ => "?"  | 
 | 11 | +}  | 
 | 12 | +----  | 
 | 13 | + | 
 | 14 | +Notablemente, __match__ es una expresión, lo que significa que produce un resultado. Además, ofrece:  | 
 | 15 | + | 
 | 16 | +*   Parámetros nombrados: ``case i: Int => "Int " + i``  | 
 | 17 | +*   Desestructuración de objetos: ``case Some(i) => i``  | 
 | 18 | +*   Condiciones de guarda (guards): ``case Some(i) if i > 0 => "positive " + i``  | 
 | 19 | +*   Múltiples condiciones: ``case "-h" | "--help" => displayHelp``  | 
 | 20 | +*   Verificaciones en tiempo de compilación para exhaustividad  | 
 | 21 | +
  | 
 | 22 | +El __**Pattern matching**__ es una gran característica que nos ahorra escribir cadenas de ramas `if-then-else`. Reduce la cantidad de código mientras se enfoca en las partes relevantes.  | 
 | 23 | + | 
 | 24 | +==== Fundamentos de Match para Java  | 
 | 25 | + | 
 | 26 | +Vavr proporciona una API de coincidencia (__match__) que es similar a la de Scala. Se habilita agregando el siguiente import a nuestra aplicación:  | 
 | 27 | + | 
 | 28 | +[source,java]  | 
 | 29 | +----  | 
 | 30 | +import static io.vavr.API.*;  | 
 | 31 | +----  | 
 | 32 | + | 
 | 33 | +Teniendo los métodos estáticos __Match__, __Case__ y los __patrones atómicos (__atomic patterns__)__  | 
 | 34 | + | 
 | 35 | +*   ``$()`` - patrón comodín (__wildcard__)  | 
 | 36 | +*   ``$(value)`` - patrón de igualdad  | 
 | 37 | +*   ``$(predicate)`` - patrón condicional  | 
 | 38 | + | 
 | 39 | +En el alcance, el ejemplo inicial de Scala puede expresarse de esta manera:  | 
 | 40 | + | 
 | 41 | +[source,java]  | 
 | 42 | +----  | 
 | 43 | +String s = Match(i).of(    | 
 | 44 | +    Case($(1), "one"),  | 
 | 45 | +    Case($(2), "two"),  | 
 | 46 | +    Case($(), "?")  | 
 | 47 | +);  | 
 | 48 | +----  | 
 | 49 | + | 
 | 50 | +⚡ Usamos nombres de métodos en mayúsculas uniformes porque 'case' es una palabra clave en Java. Esto hace que la API sea especial.  | 
 | 51 | + | 
 | 52 | +===== Exhaustividad (__Exhaustiveness__)  | 
 | 53 | + | 
 | 54 | +El último patrón comodín (__wildcard__) ``$()`` nos protege de un `MatchError`, que se lanza si ningún caso coincide.  | 
 | 55 | + | 
 | 56 | +Debido a que no podemos realizar verificaciones de exhaustividad como lo hace el compilador de Scala, ofrecemos la posibilidad de devolver un resultado opcional:  | 
 | 57 | + | 
 | 58 | +[source,java]  | 
 | 59 | +----  | 
 | 60 | +Option<String> s = Match(i).option(    | 
 | 61 | +    Case($(0), "zero")  | 
 | 62 | +);  | 
 | 63 | +----  | 
 | 64 | + | 
 | 65 | +===== Syntactic Sugar (__Azúcar Sintáctico__)  | 
 | 66 | + | 
 | 67 | +Como se mostró anteriormente, ``Case`` permite coincidir con patrones condicionales.  | 
 | 68 | + | 
 | 69 | +[source,java]  | 
 | 70 | +----  | 
 | 71 | +Case($(predicate), ...)  | 
 | 72 | +----  | 
 | 73 | + | 
 | 74 | +Vavr ofrece un conjunto de predicados predeterminados.  | 
 | 75 | + | 
 | 76 | +[source,java]  | 
 | 77 | +----  | 
 | 78 | +import static io.vavr.Predicates.*;  | 
 | 79 | +----  | 
 | 80 | + | 
 | 81 | +Estos pueden usarse para expresar el ejemplo inicial de Scala de la siguiente manera:  | 
 | 82 | + | 
 | 83 | +[source,java]  | 
 | 84 | +----  | 
 | 85 | +String s = Match(i).of(    | 
 | 86 | +    Case($(is(1)), "one"),  | 
 | 87 | +    Case($(is(2)), "two"),  | 
 | 88 | +    Case($(), "?")  | 
 | 89 | +);  | 
 | 90 | +----  | 
 | 91 | + | 
 | 92 | +**Condiciones Múltiples**  | 
 | 93 | + | 
 | 94 | +Usamos el predicado ``isIn`` para verificar múltiples condiciones:  | 
 | 95 | + | 
 | 96 | +[source,java]  | 
 | 97 | +----  | 
 | 98 | +Case($(isIn("-h", "--help")), ...)  | 
 | 99 | +----  | 
 | 100 | + | 
 | 101 | +**Realizando Efectos Secundarios**  | 
 | 102 | + | 
 | 103 | +`Match` actúa como una expresión y produce un valor. Para realizar efectos secundarios (__side-effects__), necesitamos usar la función auxiliar ``run`` que devuelve ``Void``:  | 
 | 104 | + | 
 | 105 | +[source,java]  | 
 | 106 | +----  | 
 | 107 | +Match(arg).of(    | 
 | 108 | +    Case($(isIn("-h", "--help")), o -> run(this::displayHelp)),  | 
 | 109 | +    Case($(isIn("-v", "--version")), o -> run(this::displayVersion)),  | 
 | 110 | +    Case($(), o -> run(() -> {  | 
 | 111 | +        throw new IllegalArgumentException(arg);  | 
 | 112 | +    }))  | 
 | 113 | +);  | 
 | 114 | +----  | 
 | 115 | + | 
 | 116 | +⚡ ``run`` se utiliza para evitar ambigüedades y porque ``void`` no es un valor de retorno válido en Java.  | 
 | 117 | + | 
 | 118 | +*Precaución:* ``run`` no debe usarse como valor de retorno directo, es decir, fuera del cuerpo de una lambda:  | 
 | 119 | + | 
 | 120 | +[source,java]  | 
 | 121 | +----  | 
 | 122 | +// Incorrecto  | 
 | 123 | +Case($(isIn("-h", "--help")), run(this::displayHelp))  | 
 | 124 | +----  | 
 | 125 | + | 
 | 126 | +De lo contrario, los `Case` se evaluarán de forma anticipada __antes__ de que los patrones sean comparados, lo que rompe toda la expresión `Match`. En su lugar, se debe usar dentro del cuerpo de una lambda:  | 
 | 127 | + | 
 | 128 | +[source,java]  | 
 | 129 | +----  | 
 | 130 | +// Correcto  | 
 | 131 | +Case($(isIn("-h", "--help")), o -> run(this::displayHelp))  | 
 | 132 | +----  | 
 | 133 | + | 
 | 134 | +Como podemos ver, ``run`` es propenso a errores si no se usa correctamente. Ten cuidado. Estamos considerando marcarlo como obsoleto en una versión futura y, tal vez, proporcionar una mejor API para realizar efectos secundarios.  | 
 | 135 | + | 
 | 136 | +===== Parámetros Nombrados  | 
 | 137 | + | 
 | 138 | +Vavr aprovecha las lambdas para proporcionar parámetros nombrados para los valores coincidentes.  | 
 | 139 | + | 
 | 140 | +[source,java]  | 
 | 141 | +----  | 
 | 142 | +Number plusOne = Match(obj).of(    | 
 | 143 | +    Case($(instanceOf(Integer.class)), i -> i + 1),  | 
 | 144 | +    Case($(instanceOf(Double.class)), d -> d + 1),  | 
 | 145 | +    Case($(), o -> { throw new NumberFormatException(); })  | 
 | 146 | +);  | 
 | 147 | +----  | 
 | 148 | + | 
 | 149 | +Hasta ahora, hemos coincidido valores directamente utilizando patrones atómicos. Si un patrón atómico coincide, el tipo correcto del objeto coincidente se infiere del contexto del patrón.  | 
 | 150 | + | 
 | 151 | +A continuación, exploraremos patrones recursivos que pueden coincidir con gráficos de objetos de profundidad (teóricamente) arbitraria.  | 
 | 152 | + | 
 | 153 | +===== Descomposición de Objetos  | 
 | 154 | + | 
 | 155 | +En Java usamos constructores para instanciar clases. Entendemos la __descomposición de objetos__ como la destrucción de objetos en sus partes.  | 
 | 156 | + | 
 | 157 | +Mientras que un constructor es una __función__ que se __aplica__ a los argumentos y devuelve una nueva instancia, un deconstructor es una función que toma una instancia y devuelve sus partes. Decimos que un objeto está __descompuesto__.  | 
 | 158 | + | 
 | 159 | +La destrucción de objetos no necesariamente es una operación única. Por ejemplo, un `LocalDate` puede descomponerse en:  | 
 | 160 | + | 
 | 161 | +*   Los componentes de año, mes y día.  | 
 | 162 | +*   El valor `long` que representa los milisegundos desde la época de un `Instant` correspondiente.  | 
 | 163 | +*   etc.  | 
 | 164 | + | 
 | 165 | +==== Patrones  | 
 | 166 | + | 
 | 167 | +En Vavr usamos patrones para definir cómo se deconstruye una instancia de un tipo específico. Estos patrones pueden usarse junto con la API de coincidencia (`Match`).  | 
 | 168 | + | 
 | 169 | +===== Patrones Predefinidos  | 
 | 170 | + | 
 | 171 | +Para muchos tipos de Vavr ya existen patrones de coincidencia predefinidos. Estos se importan mediante  | 
 | 172 | + | 
 | 173 | +[source,java]  | 
 | 174 | +----  | 
 | 175 | +import static io.vavr.Patterns.*;  | 
 | 176 | +----  | 
 | 177 | + | 
 | 178 | +Por ejemplo, ahora podemos coincidir con el resultado de un `Try`:  | 
 | 179 | + | 
 | 180 | +[source,java]  | 
 | 181 | +----  | 
 | 182 | +Match(_try).of(    | 
 | 183 | +    Case($Success($()), value -> ...),  | 
 | 184 | +    Case($Failure($()), x -> ...)  | 
 | 185 | +);  | 
 | 186 | +----  | 
 | 187 | + | 
 | 188 | +⚡ Un primer prototipo de la API de coincidencia (`Match`) de Vavr permitía extraer una selección definida por el usuario de objetos a partir de un patrón de coincidencia. Sin el soporte adecuado del compilador, esto no es práctico porque el número de métodos generados crecía exponencialmente. La API actual hace un compromiso: todos los patrones se coinciden, pero solo los patrones raíz son __descompuestos__.  | 
 | 189 | + | 
 | 190 | +[source,java]  | 
 | 191 | +----  | 
 | 192 | +Match(_try).of(    | 
 | 193 | +    Case($Success($Tuple2($("a"), $())), tuple2 -> ...),  | 
 | 194 | +    Case($Failure($(instanceOf(Error.class))), error -> ...)  | 
 | 195 | +);  | 
 | 196 | +----  | 
 | 197 | + | 
 | 198 | +Aquí los patrones raíz son `Success` y `Failure`. Estos se descomponen en `Tuple2` y `Error`, teniendo los tipos genéricos correctos.  | 
 | 199 | + | 
 | 200 | +⚡ Los tipos profundamente anidados se infieren según el argumento de `Match` y __**not**__ según los patrones coincidentes.  | 
 | 201 | + | 
 | 202 | +===== Patrones Definidos por el Usuario  | 
 | 203 | + | 
 | 204 | +Es esencial poder descomponer objetos arbitrarios, incluidas las instancias de clases finales. Vavr hace esto de forma declarativa al proporcionar las anotaciones en tiempo de compilación ``@Patterns`` y ``@Unapply``.  | 
 | 205 | + | 
 | 206 | +Para habilitar el procesador de anotaciones, el artefacto http://search.maven.org/#search%7Cga%7C1%7Cvavr-match[vavr-match] debe añadirse como dependencia del proyecto.  | 
 | 207 | + | 
 | 208 | +⚡ Nota: Por supuesto, los patrones pueden implementarse directamente sin usar el generador de código. Para más información, consulta el código fuente generado.  | 
 | 209 | + | 
 | 210 | +[source,java]  | 
 | 211 | +----  | 
 | 212 | +import io.vavr.match.annotation.*;  | 
 | 213 | +
  | 
 | 214 | +@Patterns  | 
 | 215 | +class My {  | 
 | 216 | +
  | 
 | 217 | +    @Unapply  | 
 | 218 | +    static <T> Tuple1<T> Optional(java.util.Optional<T> optional) {  | 
 | 219 | +        return Tuple.of(optional.orElse(null));  | 
 | 220 | +    }  | 
 | 221 | +}  | 
 | 222 | +----  | 
 | 223 | + | 
 | 224 | +El procesador de anotaciones coloca un archivo `MyPatterns` en el mismo paquete (por defecto en `target/generated-sources`). También se admiten clases internas. Caso especial: si el nombre de la clase es `$`, el nombre de la clase generada será simplemente `Patterns`, sin prefijo.  | 
 | 225 | + | 
 | 226 | +===== Guardas (__Guards__)  | 
 | 227 | + | 
 | 228 | +Ahora podemos coincidir con objetos `Optionals` utilizando __guards__.  | 
 | 229 | + | 
 | 230 | +[source,java]  | 
 | 231 | +----  | 
 | 232 | +Match(optional).of(    | 
 | 233 | +    Case($Optional($(v -> v != null)), "defined"),  | 
 | 234 | +    Case($Optional($(v -> v == null)), "empty")  | 
 | 235 | +);  | 
 | 236 | +----  | 
 | 237 | + | 
 | 238 | +Los predicados podrían simplificarse implementando ``isNull`` y ``isNotNull``.  | 
 | 239 | + | 
 | 240 | +⚡ ¡Y sí, extraer un `null` es extraño! En lugar de usar el `Optional` de Java, prueba con la `Option` de Vavr.  | 
 | 241 | + | 
 | 242 | +[source,java]  | 
 | 243 | +----  | 
 | 244 | +Match(option).of(    | 
 | 245 | +    Case($Some($()), "defined"),  | 
 | 246 | +    Case($None(), "empty")  | 
 | 247 | +);  | 
 | 248 | +----  | 
0 commit comments