Control de Flujo
Estructuras para tomar decisiones y manejar diferentes casos.
case
case evalúa una expresión contra múltiples patrones:
case File.read("config.txt") do
{:ok, contenido} ->
IO.puts("Contenido: #{contenido}")
{:error, :enoent} ->
IO.puts("Archivo no encontrado")
{:error, razon} ->
IO.puts("Error: #{razon}")
end
case con guards
case numero do
n when n < 0 -> :negativo
0 -> :cero
n when n > 0 -> :positivo
end
El patrón catch-all
case valor do
1 -> "uno"
2 -> "dos"
_ -> "otro" # Captura cualquier otro valor
end
Si ningún patrón coincide y no hay catch-all, Elixir lanza un error. Siempre incluye un caso _ si no estás seguro de cubrir todos los casos.
cond
cond evalúa múltiples condiciones booleanas (como if/else if encadenados):
cond do
edad < 13 -> "niño"
edad < 18 -> "adolescente"
edad < 65 -> "adulto"
true -> "senior" # Equivale a "else"
end
case: Cuando comparas un valor contra patrones.
cond: Cuando evalúas múltiples condiciones independientes.
if y unless
Para condiciones simples:
# if básico
if edad >= 18 do
"Mayor de edad"
end
# if con else
if edad >= 18 do
"Mayor de edad"
else
"Menor de edad"
end
# Forma de una línea
if activo, do: "Sí", else: "No"
# unless (contrario de if)
unless lista_vacia, do: "Tiene elementos"
En Elixir, if es una expresión que retorna un valor:
mensaje = if hora < 12, do: "Buenos días", else: "Buenas tardes"
with
with es perfecto para encadenar operaciones que pueden fallar:
# Sin with (anidación profunda)
case File.read("config.json") do
{:ok, contenido} ->
case Jason.decode(contenido) do
{:ok, datos} ->
case Map.fetch(datos, "usuario") do
{:ok, usuario} -> {:ok, usuario}
:error -> {:error, "usuario no encontrado"}
end
error -> error
end
error -> error
end
# Con with (limpio y lineal)
with {:ok, contenido} <- File.read("config.json"),
{:ok, datos} <- Jason.decode(contenido),
{:ok, usuario} <- Map.fetch(datos, "usuario") do
{:ok, usuario}
else
:error -> {:error, "usuario no encontrado"}
{:error, razon} -> {:error, razon}
end
with sin else
Si omites else, el primer valor que no haga match se retorna directamente:
with {:ok, a} <- obtener_a(),
{:ok, b} <- obtener_b(a) do
{:ok, a + b}
end
# Si obtener_a() retorna {:error, :no_encontrado}, with retorna eso
Manejo de errores
try/rescue
try do
String.to_integer("no soy número")
rescue
ArgumentError -> "No es un número válido"
e in RuntimeError -> "Error: #{e.message}"
end
try/catch (para throws)
try do
throw({:error, "algo salió mal"})
catch
{:error, mensaje} -> IO.puts("Capturado: #{mensaje}")
end
after
try do
# operación riesgosa
rescue
e -> IO.puts("Error: #{Exception.message(e)}")
after
# Siempre se ejecuta (limpieza)
IO.puts("Limpieza completa")
end
En Elixir, es preferible usar pattern matching con tuplas {:ok, valor} / {:error, razon} en lugar de excepciones. Reserva try/rescue para errores verdaderamente excepcionales.
raise y reraise
# Lanzar excepción
raise "Algo salió mal"
raise ArgumentError, message: "argumento inválido"
# Definir excepción personalizada
defmodule MiError do
defexception message: "error por defecto"
end
raise MiError, message: "error específico"
Funciones con ! (bang)
Por convención, las funciones que terminan en ! lanzan excepciones en lugar de retornar tuplas:
# Retorna {:ok, contenido} o {:error, razon}
File.read("archivo.txt")
# Retorna contenido directamente o lanza excepción
File.read!("archivo.txt")
# Útil cuando sabes que el archivo existe
config = File.read!("config.json") |> Jason.decode!()
Ejemplo completo: Validación de usuario
defmodule Validador do
def validar_usuario(params) do
with {:ok, nombre} <- validar_nombre(params),
{:ok, email} <- validar_email(params),
{:ok, edad} <- validar_edad(params) do
{:ok, %{nombre: nombre, email: email, edad: edad}}
end
end
defp validar_nombre(%{"nombre" => nombre}) when is_binary(nombre) do
if String.length(nombre) >= 2 do
{:ok, nombre}
else
{:error, "nombre muy corto"}
end
end
defp validar_nombre(_), do: {:error, "nombre requerido"}
defp validar_email(%{"email" => email}) when is_binary(email) do
if String.contains?(email, "@") do
{:ok, email}
else
{:error, "email inválido"}
end
end
defp validar_email(_), do: {:error, "email requerido"}
defp validar_edad(%{"edad" => edad}) when is_integer(edad) and edad >= 18 do
{:ok, edad}
end
defp validar_edad(%{"edad" => _}), do: {:error, "debe ser mayor de edad"}
defp validar_edad(_), do: {:error, "edad requerida"}
end
# Uso
iex> Validador.validar_usuario(%{"nombre" => "Ana", "email" => "[email protected]", "edad" => 25})
{:ok, %{nombre: "Ana", email: "[email protected]", edad: 25}}
iex> Validador.validar_usuario(%{"nombre" => "A"})
{:error, "nombre muy corto"}
Crea una función que tome una nota (0-100) y retorne:
- "Insuficiente" (0-49)
- "Suficiente" (50-59)
- "Bien" (60-69)
- "Notable" (70-89)
- "Sobresaliente" (90-100)
Usa cond y maneja el caso de notas inválidas.
Crea una función que use with para:
- Leer un archivo de configuración
- Parsear su contenido como JSON
- Extraer una clave específica
- Retornar
{:ok, valor}o un error descriptivo