El pattern matching (se podría traducir como búsqueda de patrones) es otro concepto traído de la programación funcional que (como la propia programación funcional, de hecho) está tomando cada vez más importancia.
¿Pero… en qué consiste exactamente?
Muchos lenguajes tienen este concepto, aunque no todos con el mismo nivel de profundidad. En este artículo hablaremos un poco del pattern matching , su importancia y veremos algunos ejemplos. En la actualidad ECMAScript ya dispone de ciertas capacidades de patrones y se está valorando incluirlo en C# 7, por lo que conviene ir aprendiendo sobre el asunto.
Nota: Aunque los conceptos son genéricos, el lenguaje que vamos a usar para los mismos es Elixir. Elixir es un lenguaje funcional que se ejecuta bajo BEAM, la máquina virtual de Erlang. La razón para escojer Elixir es que el pattern matching es parte intrínseca de dicho lenguaje. Los ejemplos de Elixir se mostrarán bajo el iex (el REPL del lenguaje). En estos ejemplos, el código en cursiva es lo que sale por pantalla al ejecutar los comandos. Para iniciar el REPL de Elixir, basta irse al directorio donde se haya instalado Elixir, abrir una consola de línea de comandos y teclear iex.
¡Empcemos!
El siguiente código muestra en acción el pattern matching más sencillo:
iex(1)> a = 10
10
En este ejemplo “iex(1) >” es el prompt de entrada de Elixir. El comando tecleado es a = 10 y vemos que el resultado es 10 (en Elixir como en la mayoría de lenguajes funcionales todo son expresiones que devuelven un valor). Probablemente estás pensando "¿qué tiene que ver una asignación de una variable con pattern matching?". Pero ahí radica la clave para entender el pattern matching: Cuando se aplica tenemos dos expresiones (patrones) que se intentan igualar uno al otro y además los valores libres (variables) toman los valores necesarios para que estos patrones se igualen. En este caso el patron “a” se intenta igualar al patrón “10” y ello es posible si el valor de a pasa a ser 10.
Si todavía te quedas con la idea que eso es una mera asignación de variable, vayamos con otro ejemplo:
iex(2)> 9 = a
** (MatchError) no match of right hand side value: 10
Esta sentencia nos da un error. Pero antes de que pienses “normal, no tiene sentido asignar un valor a un número”, observa el mensaje de error. Elixir no se queja de que 9 sea una constante o de que no pueda asignar un valor a un value o cualquier otro mensaje similar. Elixir, nos devuelve un error de pattern matching. Nos dice que los dos patrones (9) y (a) no pueden ser igualados, porque el valor de a ya es 10. Por supuesto, podrías pensar que Elixir podría modificar el valor de a para que pasase a ser 9, de forma análoga al primer ejemplo. Pues sí, podría hacerlo si el pattern matching de Elixir funcionase de esa manera. Pero Elixir no hace eso. Recuerda que cada lenguaje implementa el pattern matching a su manera. Lo importante es que entiendas el concepto.
Ahora bien, observa que el siguiente código sí que funciona:
iex(2)> 10 = a
10
Ahora ¡Elixir no se queja! ¿Cómo va a hacerlo si ambos patrones tienen el mismo valor (10)?. Por lo tanto Elixir puede aplicar pattern matching sin problemas y el código es válido.
Veamos otro ejemplo más interesante para ir pillándole el truco al concepto:
iex(3)> [b, 10] = [20, a]
[20, 10]
Este código es correcto. Elixir intenta el pattern matching entre cada elemento de las dos listas. Por ello b va a tomar el valor de 20. Ahora podemos ver como a vale 10 y b 20:
iex(4)> a 10 iex(5)> b 20
Perfecto… ahora hagamos algo realmente interesante. ¿Cómo intercambiarías los valores de a y b?
Probablemente tu respuesta sea usando una variable temporal y realizar tres asignaciones. Pero intenta pensar en pattern matching. Es mucho más sencillo:
iex(6)> [b,a] = [a,b] [10, 20] iex(7)>
a
20
iex(8)>
b
10
Sencillo y muy elegante, ¿no te parece? Aquí ya estamos empezando a explotar el poder que se oculta tras el pattern matching.
Veamos otro ejemplo. Primero definimos una función en Elixir. En Elixir las funciones se distinguen por su nombre y su aridad (la aridad de una función es el número de parámetros que acepta). Así, en Elixir, una función foo con dos parámetros se llama foo/2 y una función foo con un parámetro se llama foo/1. Por lo general en Elixir las funciones se declaran dentro de módulos (que suelen ser ficheros aparte). En este caso, vamos a declarar el módulo desde el propio iex, aunque eso (por practicidad) no suele hacerse:
iex(9)> defmodule Test do
...(9)> def foo("") do
...(9)> {:error}
...(9)> end
...(9)> def foo(v) do
...(9)> {:ok, v}
...(9)> end
...(9)>
end
No he copiado la respuesta de Elixir porque no es relevante (es el propio módulo, de hecho, literalmente el bytecode del módulo). Vayamos a lo que si importa. Primero debemos saber que Elixir no permite declarar dos funciones con el mismo nombre y la misma aridad. Pero, como puedes observar en este caso lo estamos haciendo. Estamos definiendo dos veces la función foo/1.
¿Como es posible eso?
La razón es que en este caso el pattern matching permite diferenciar las llamadas. Observa que la “primera” foo/1 no tiene un parámetro, si no un valor fijo. Mientras que la segunda versión de foo/1 es la que acepta un parámetro (v). Debemos entender que Elixir aplica pattern matching al llamar a funciones:
iex(10)>
Test.foo("")
{:error}
iex(11)> Test.foo("CampusMVP")
{:ok, "CampusMVP"}
Si llamamos a Test.foo con una cadena vacía, por pattern matching se ejecuta la función que devuelve {:error}. Con cualquier otro valor se ejecuta la otra función. Observa como hemos usado el pattern matching para diferenciar el caso de parámetro inválido (cadena vacía) del caso correcto. No es necesario usar un condicional (if) para validar que el valor del parámetro no es una cadena vacía.
En los lenguajes con pattern matching la necesidad de usar condicionales se reduce mucho.
Ahora la pregunta es… ¿cómo podemos saber si una llamada a Test.foo ha ido bien? Pues de nuevo gracias al pattern matching. Si la llamada ha ido bien el patrón a enlazar será de la forma {:ok, _} (donde el _ es cualquier cosa). Si la llamada ha ido mal el patrón será {:error}:
iex(25)> {:ok, _}
= Test.foo("")
** (MatchError) no match of right hand side value: {:error}
iex(25)>
{:error} = Test.foo("")
{:error}
Todos los lenguajes con pattern matching tienen alguna sentencia para especificar varios patrones y devolver un valor según el patrón elegido. Elixir no es menos y para ello dispone de la construcción case:
iex(12)> result = case Test.foo(42) do
...(12)> {:ok, v} -> v
...(12)> {:error} -> -1
...(12)> end
42
iex(13)> result
42
Este último ejemplo encierra mucha de la potencia del pattern matching. En un case le damos a Elixir todos los posibles patrones y para cada patrón (después de la flecha –>) el valor a devolver si ese patrón es el aplicado.
Observa como si la función Test.foo/1 devuelve un patrón que sea {:ok,v} (y eso implica que v toma un valor) entonces se devuelve v (el valor que haya tomado para poder aplicar el patrón). Por otro lado si el patrón devuelto por Test.foo/1 es {:error} entonces se devuelve –1.
Recuerda que en Elixir (como en muchos lenguajes funcionales) todo son expresiones que devuelven un valor. Y case es una expresión que devuelve un valor, por eso podemos asignar este valor a result (aunque ya hemos visto antes que realmente eso no es una asignación, es otro pattern matching).
En este ejemplo, el valor final de result es 42. Si la llamada a Test.foo/1 hubiese sido con cadena vacía, entonces result valdría –1. Y no ha sido necesario ningún if.
Lo dejamos aquí. Lo importante de este artículo no es que aprendas la sintaxis de Elixir sino el concepto de pattern matching.
Muchos lenguajes funcionales (Haskell, F#, Scala y otros) lo incorporan (cada cual con sus peculiaridades y limitaciones). Pero lo importante es entenderlo y empezar a pensar de este modo, porque puede simplificar mucho nuestro código…
Si eres de JavaScript, ECMAScript 2015 tiene algunas capacidades de pattern matching y si eres más de C# pues bueno… están valorando la inclusión de pattern matching en C#7. ¡Así, que yo te recomiendo que te familiarices con el concepto!
Foto de dominio público, vía VisualHunt