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")
No puedes leer _

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
Orden de las cláusulas

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
Ejercicio 3.1 Extracción de datos Básico

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
Ejercicio 3.2 Función con pattern matching Intermedio

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}
Ejercicio 3.3 Suma recursiva Intermedio

Implementa una función sumar/1 que sume todos los elementos de una lista usando pattern matching y recursión (sin usar Enum.sum).