Funciones y Módulos

Organiza tu código en funciones reutilizables y módulos bien estructurados.

Funciones anónimas

Las funciones son ciudadanos de primera clase en Elixir. Puedes crearlas, pasarlas como argumentos y retornarlas desde otras funciones:

# Crear una función anónima
iex> suma = fn a, b -> a + b end
#Function<...>

# Llamarla con punto
iex> suma.(2, 3)
5

# Sintaxis corta con &
iex> suma = &(&1 + &2)
iex> suma.(2, 3)
5
¿Por qué el punto?

El punto (.) distingue las funciones anónimas de las funciones con nombre. suma.(1, 2) llama a una función anónima, mientras Modulo.suma(1, 2) llama a una función con nombre.

Funciones con múltiples cláusulas

saludo = fn
  "Ana" -> "¡Hola Ana, bienvenida!"
  "Pedro" -> "¡Qué tal Pedro!"
  nombre -> "Hola, #{nombre}"
end

iex> saludo.("Ana")
"¡Hola Ana, bienvenida!"
iex> saludo.("Carlos")
"Hola, Carlos"

El operador capture (&)

# Capturar una función existente
iex> mayusculas = &String.upcase/1
iex> mayusculas.("hola")
"HOLA"

# Sintaxis corta para funciones simples
iex> doble = &(&1 * 2)
iex> doble.(5)
10

# &1, &2, etc. son los parámetros
iex> concatenar = &("#{&1} y #{&2}")
iex> concatenar.("A", "B")
"A y B"

Módulos

Los módulos agrupan funciones relacionadas:

defmodule Matematica do
  def suma(a, b) do
    a + b
  end

  def resta(a, b) do
    a - b
  end

  # Sintaxis corta para funciones de una línea
  def doble(n), do: n * 2
end

iex> Matematica.suma(1, 2)
3
iex> Matematica.doble(5)
10

Funciones públicas vs privadas

defmodule Saludo do
  # Función pública (def)
  def hola(nombre) do
    formatear("Hola", nombre)
  end

  # Función privada (defp) - solo accesible dentro del módulo
  defp formatear(saludo, nombre) do
    "#{saludo}, #{nombre}!"
  end
end

iex> Saludo.hola("Ana")
"Hola, Ana!"
iex> Saludo.formatear("Hola", "Ana")
** (UndefinedFunctionError) function Saludo.formatear/2 is undefined or private

Aridad de funciones

La aridad es el número de argumentos. Funciones con el mismo nombre pero diferente aridad son funciones diferentes:

defmodule Ejemplo do
  def saludo, do: "Hola"                  # saludo/0
  def saludo(nombre), do: "Hola, #{nombre}"  # saludo/1
  def saludo(saludo, nombre), do: "#{saludo}, #{nombre}"  # saludo/2
end

iex> Ejemplo.saludo()
"Hola"
iex> Ejemplo.saludo("Ana")
"Hola, Ana"
iex> Ejemplo.saludo("Buenos días", "Ana")
"Buenos días, Ana"

Valores por defecto

defmodule Saludo do
  def hola(nombre, saludo \\ "Hola") do
    "#{saludo}, #{nombre}!"
  end
end

iex> Saludo.hola("Ana")
"Hola, Ana!"
iex> Saludo.hola("Ana", "Buenos días")
"Buenos días, Ana!"

Guards (Guardas)

Los guards añaden condiciones adicionales al pattern matching:

defmodule Clasificador do
  def tipo(n) when is_integer(n) and n < 0 do
    :negativo
  end

  def tipo(0), do: :cero

  def tipo(n) when is_integer(n) and n > 0 do
    :positivo
  end

  def tipo(_), do: :no_es_entero
end

iex> Clasificador.tipo(-5)
:negativo
iex> Clasificador.tipo(0)
:cero
iex> Clasificador.tipo(10)
:positivo
iex> Clasificador.tipo("texto")
:no_es_entero

Funciones permitidas en guards

CategoríaFunciones
Comparación==, !=, ===, !==, <, >, <=, >=
Booleanosand, or, not
Aritméticos+, -, *, /
Tipois_atom, is_binary, is_integer, is_float, is_list, is_map, is_nil, is_number, is_tuple
Otrosabs, div, rem, length, map_size, tuple_size

El operador pipe (|>)

El pipe pasa el resultado de una expresión como primer argumento de la siguiente:

# Sin pipe (difícil de leer)
resultado = String.split(String.trim(String.downcase("  HOLA MUNDO  ")))

# Con pipe (flujo claro de datos)
resultado = "  HOLA MUNDO  "
            |> String.downcase()
            |> String.trim()
            |> String.split()

# resultado: ["hola", "mundo"]
El pipe es tu amigo

Usa el pipe para expresar transformaciones de datos de forma clara. Cada paso debe ser una transformación simple y el flujo debe leerse de arriba a abajo.

Ejemplo práctico

defmodule Procesador do
  def procesar_nombres(texto) do
    texto
    |> String.split(",")           # ["ana", " pedro ", "MARIA"]
    |> Enum.map(&String.trim/1)     # ["ana", "pedro", "MARIA"]
    |> Enum.map(&String.capitalize/1) # ["Ana", "Pedro", "Maria"]
    |> Enum.sort()                  # ["Ana", "Maria", "Pedro"]
  end
end

iex> Procesador.procesar_nombres("ana, pedro , MARIA")
["Ana", "Maria", "Pedro"]

Módulos anidados

defmodule MiApp.Usuarios.Validador do
  def validar_email(email) do
    String.contains?(email, "@")
  end
end

# Equivalente a:
defmodule MiApp do
  defmodule Usuarios do
    defmodule Validador do
      def validar_email(email) do
        String.contains?(email, "@")
      end
    end
  end
end

Atributos de módulo

defmodule Configuracion do
  # Constantes (evaluadas en compilación)
  @version "1.0.0"
  @autor "Tu Nombre"

  def version, do: @version
  def info, do: "#{@autor} - v#{@version}"
end

iex> Configuracion.version()
"1.0.0"
Documentación con @doc

Usa @doc y @moduledoc para documentar tu código:

defmodule MiModulo do
  @moduledoc """
  Este módulo hace cosas increíbles.
  """

  @doc """
  Suma dos números.

  ## Ejemplos

      iex> MiModulo.suma(1, 2)
      3
  """
  def suma(a, b), do: a + b
end
Ejercicio 4.1 Módulo de utilidades Básico

Crea un módulo StringUtils con las siguientes funciones:

  • reverso/1 - invierte un string
  • palabras/1 - cuenta las palabras en un string
  • truncar/2 - trunca un string a n caracteres, añadiendo "..." si es necesario
Ejercicio 4.2 Pipeline de transformación Intermedio

Crea una función que tome una lista de números y usando pipes:

  • Filtre solo los positivos
  • Los duplique
  • Los ordene de mayor a menor
  • Tome solo los primeros 3