Pattern Matching
El superpoder de Elixir que cambiará tu forma de programar.
El operador = no es asignación
En Elixir, = es el operador de match (coincidencia), no de asignación. Intenta hacer que ambos lados sean iguales:
iex> x = 1
1
iex> 1 = x # ¡Esto funciona!
1
iex> 2 = x # Esto falla
** (MatchError) no match of right hand side value: 1
Cuando hay una variable del lado izquierdo, Elixir le asigna el valor necesario para que el match funcione:
iex> x = 1 # x se enlaza a 1 para hacer match
iex> x = 2 # x se re-enlaza a 2
2
Pattern matching con tuplas
# Extraer valores de una tupla
iex> {a, b, c} = {1, 2, 3}
{1, 2, 3}
iex> a
1
iex> b
2
# Debe coincidir la estructura
iex> {a, b} = {1, 2, 3}
** (MatchError) no match of right hand side value: {1, 2, 3}
El patrón {:ok, valor} / {:error, razón}
# Patrón muy común en Elixir
{:ok, contenido} = File.read("archivo.txt")
# Si el archivo no existe, el match falla
# Esto es intencional - falla rápido si algo sale mal
Pattern matching con listas
# Extraer elementos
iex> [a, b, c] = [1, 2, 3]
iex> a
1
# Cabeza y cola con |
iex> [cabeza | cola] = [1, 2, 3]
iex> cabeza
1
iex> cola
[2, 3]
# Múltiples elementos al inicio
iex> [a, b | resto] = [1, 2, 3, 4]
iex> a
1
iex> b
2
iex> resto
[3, 4]
Pattern matching con mapas
# Extraer valores específicos
iex> %{nombre: nombre} = %{nombre: "Ana", edad: 30}
iex> nombre
"Ana"
# El patrón puede tener menos claves que el mapa
iex> %{edad: e} = %{nombre: "Ana", edad: 30, ciudad: "Madrid"}
iex> e
30
# Pero las claves del patrón deben existir
iex> %{salario: s} = %{nombre: "Ana"}
** (MatchError) no match
El operador pin (^)
Usa ^ cuando quieras comparar contra el valor actual de una variable en lugar de re-enlazarla:
iex> x = 1
1
iex> x = 2 # Re-enlaza x
2
iex> ^x = 2 # Compara contra el valor de x
2
iex> ^x = 3 # Falla porque x es 2
** (MatchError) no match of right hand side value: 3
Pin en patrones complejos
iex> clave = :nombre
iex> %{^clave => valor} = %{nombre: "Ana"}
iex> valor
"Ana"
iex> esperado = 1
iex> [^esperado, segundo] = [1, 2] # Funciona
iex> [^esperado, segundo] = [3, 4] # Falla
** (MatchError)
El guión bajo (_)
Usa _ para ignorar valores que no te interesan:
# Ignorar valores
iex> {_, segundo, _} = {1, 2, 3}
iex> segundo
2
# Ignorar la cola
iex> [primero | _] = [1, 2, 3, 4, 5]
iex> primero
1
# Variables con _ al inicio también se ignoran
# pero documentan qué estás ignorando
iex> {:ok, _contenido} = File.read("archivo.txt")
Las variables que empiezan con _ no pueden ser leídas. Elixir dará un error si intentas usarlas:
iex> {_, b} = {1, 2}
iex> _
** (CompileError) invalid use of _
Pattern matching en funciones
Una de las aplicaciones más poderosas es en definiciones de funciones:
defmodule Saludo do
# Diferentes cláusulas para diferentes patrones
def decir({:ok, nombre}) do
"¡Hola, #{nombre}!"
end
def decir({:error, razon}) do
"Error: #{razon}"
end
end
iex> Saludo.decir({:ok, "Ana"})
"¡Hola, Ana!"
iex> Saludo.decir({:error, "no encontrado"})
"Error: no encontrado"
Ejemplo: Calcular longitud de lista
defmodule MiLista do
# Caso base: lista vacía
def longitud([]), do: 0
# Caso recursivo: ignoramos la cabeza, contamos la cola
def longitud([_cabeza | cola]) do
1 + longitud(cola)
end
end
iex> MiLista.longitud([1, 2, 3])
3
Pattern matching en case
resultado = File.read("config.txt")
case resultado do
{:ok, contenido} ->
IO.puts("Contenido: #{contenido}")
{:error, :enoent} ->
IO.puts("Archivo no encontrado")
{:error, razon} ->
IO.puts("Error: #{razon}")
end
Patrones en strings
# Match al inicio de un string
iex> "Hola, " <> nombre = "Hola, Mundo"
"Hola, Mundo"
iex> nombre
"Mundo"
# Útil para parsear
def parsear_saludo("Hola, " <> nombre), do: {:ok, nombre}
def parsear_saludo(_otro), do: :error
Elixir evalúa las cláusulas de función en orden. Pon los patrones más específicos primero:
# Correcto
def f([]), do: :vacia
def f(lista), do: :no_vacia
# Incorrecto - la primera cláusula siempre hace match
def f(lista), do: :no_vacia
def f([]), do: :vacia # Nunca se ejecuta
Dado el siguiente mapa:
usuario = %{
nombre: "Carlos",
email: "[email protected]",
direccion: %{
ciudad: "Barcelona",
codigo_postal: "08001"
}
}
Usa pattern matching para extraer en una sola línea:
- El nombre del usuario
- La ciudad de la dirección
Crea un módulo Calculadora con una función operar/1 que reciba una tupla y realice operaciones:
{:suma, a, b}→ retorna a + b{:resta, a, b}→ retorna a - b{:mult, a, b}→ retorna a * b- Cualquier otra cosa → retorna
{:error, :operacion_desconocida}
Implementa una función sumar/1 que sume todos los elementos de una lista usando pattern matching y recursión (sin usar Enum.sum).