Ir al contenido principal

Introducción a los módulos en Lua

A medida que tus programas en Lua crecen, encontrarás que tener todo el código en un solo archivo se vuelve difícil de manejar. Los módulos te permiten organizar tu código en partes separadas y reutilizables. En este artículo aprenderás qué son los módulos, cómo crearlos y cómo usarlos en tus programas.

¿Qué es un módulo?

Un módulo es simplemente un archivo de Lua que contiene funciones y variables que pueden ser utilizadas en otros programas. Piensa en un módulo como una caja de herramientas: contiene las herramientas (funciones) que necesitas para realizar tareas específicas.

Por ejemplo, si estás creando un programa que necesita hacer cálculos matemáticos avanzados, podrías crear un módulo con todas esas funciones matemáticas. Luego, cualquier programa que necesite esas funciones puede simplemente usar ese módulo en lugar de tener que escribir todo el código otra vez.

Ventajas de usar módulos

Usar módulos en tus programas tiene varios beneficios importantes:

Organización: Mantiene tu código ordenado y fácil de encontrar. En lugar de tener un archivo gigante con cientos de líneas, puedes tener varios archivos más pequeños, cada uno con una función específica.

Reutilización: Una vez que creas un módulo, puedes usarlo en muchos programas diferentes. No necesitas copiar y pegar el mismo código una y otra vez.

Mantenimiento: Si necesitas corregir un error o mejorar una función, solo tienes que cambiarla en un lugar (el módulo), y todos los programas que usan ese módulo se beneficiarán automáticamente.

Colaboración: Cuando trabajas en equipo, diferentes personas pueden trabajar en módulos diferentes sin interferir entre sí.

Creando tu primer módulo

Vamos a crear un módulo simple que contenga funciones matemáticas básicas. Primero, crea un nuevo archivo llamado matematicas.lua:

-- matematicas.lua
-- Módulo con operaciones matemáticas básicas

-- Creamos una tabla que contendrá nuestras funciones
local M = {}

-- Función que suma dos números
function M.sumar(a, b)
    return a + b
end

-- Función que resta dos números
function M.restar(a, b)
    return a - b
end

-- Función que multiplica dos números
function M.multiplicar(a, b)
    return a * b
end

-- Función que divide dos números
function M.dividir(a, b)
    if b == 0 then
        return nil, "Error: no se puede dividir por cero"
    end
    return a / b
end

-- Devolvemos la tabla con nuestras funciones
return M

Analicemos lo que acabamos de hacer:

  1. Creamos una tabla llamada M (de "Módulo"). Esta tabla contendrá todas las funciones que queremos compartir.

  2. Definimos cuatro funciones: sumar, restar, multiplicar y dividir. Cada una está dentro de la tabla M, por eso usamos M.sumar, M.restar, etc.

  3. Al final del archivo, usamos return M para devolver la tabla con todas nuestras funciones.

La palabra clave local antes de M es importante: hace que la tabla solo sea accesible dentro de este archivo, lo cual es una buena práctica. Solo las funciones que asignamos a M serán visibles desde fuera.

Usando un módulo

Ahora que tenemos nuestro módulo, vamos a usarlo. Crea un nuevo archivo llamado main.lua en la misma carpeta donde guardaste matematicas.lua:

-- main.lua
-- Programa que usa nuestro módulo de matemáticas

-- Cargamos el módulo con require
local mat = require("matematicas")

-- Ahora podemos usar las funciones del módulo
print("5 + 3 =", mat.sumar(5, 3))
print("10 - 4 =", mat.restar(10, 4))
print("6 * 7 =", mat.multiplicar(6, 7))

-- La función dividir puede devolver un error
local resultado, error = mat.dividir(15, 3)
if resultado then
    print("15 / 3 =", resultado)
else
    print(error)
end

-- Intentamos dividir por cero
resultado, error = mat.dividir(10, 0)
if resultado then
    print("10 / 0 =", resultado)
else
    print(error)
end

Cuando ejecutes este programa, verás:

5 + 3 = 8
10 - 4 = 6
6 * 7 = 42
15 / 3 = 5.0
Error: no se puede dividir por cero

La función require

La función require es la que nos permite cargar módulos en Lua. Cuando escribes:

local mat = require("matematicas")

Lua hace lo siguiente:

  1. Busca un archivo llamado matematicas.lua en el directorio actual
  2. Ejecuta ese archivo
  3. Obtiene lo que el archivo devuelve (en nuestro caso, la tabla M)
  4. Guarda ese resultado en la variable mat

Es importante saber que require solo carga cada módulo una vez. Si llamas a require("matematicas") varias veces en tu programa, Lua no volverá a ejecutar el archivo cada vez, sino que reutilizará el resultado de la primera carga. Esto hace que tu programa sea más eficiente.

Nombres de archivos y módulos

El nombre que usas en require debe coincidir con el nombre del archivo del módulo, pero sin la extensión .lua. Por ejemplo:

  • Archivo: matematicas.luarequire("matematicas")
  • Archivo: utilidades.luarequire("utilidades")
  • Archivo: mi_modulo.luarequire("mi_modulo")

Si tu módulo está en una subcarpeta, incluyes el nombre de la carpeta separado por un punto:

  • Archivo: herramientas/texto.luarequire("herramientas.texto")

Funciones privadas en módulos

A veces queremos tener funciones que solo se usan dentro del módulo y no deben ser accesibles desde fuera. Para esto, definimos funciones locales que no agregamos a la tabla del módulo.

Veamos un ejemplo modificando nuestro módulo de matemáticas:

-- matematicas.lua (versión mejorada)

local M = {}

-- Función privada: solo se puede usar dentro de este módulo
local function esNumeroValido(n)
    return type(n) == "number"
end

-- Funciones públicas que validan la entrada
function M.sumar(a, b)
    if not esNumeroValido(a) or not esNumeroValido(b) then
        return nil, "Error: ambos parámetros deben ser números"
    end
    return a + b
end

function M.restar(a, b)
    if not esNumeroValido(a) or not esNumeroValido(b) then
        return nil, "Error: ambos parámetros deben ser números"
    end
    return a - b
end

-- ... resto de las funciones ...

return M

La función esNumeroValido es local y no está en la tabla M, por lo que no se puede llamar desde fuera del módulo:

local mat = require("matematicas")

-- Esto funciona
print(mat.sumar(5, 3))

-- Esto daría error porque esNumeroValido es privada
-- print(mat.esNumeroValido(5))  -- ¡No funcionaría!

Ejemplo práctico: módulo de utilidades de texto

Vamos a crear un módulo más completo que contenga funciones útiles para trabajar con texto. Crea un archivo llamado texto.lua:

-- texto.lua
-- Módulo con funciones útiles para trabajar con cadenas

local M = {}

-- Convierte la primera letra de una cadena a mayúscula
function M.capitalizar(cadena)
    if not cadena or #cadena == 0 then
        return ""
    end
    local primera = string.sub(cadena, 1, 1)
    local resto = string.sub(cadena, 2)
    return string.upper(primera) .. resto
end

-- Cuenta cuántas palabras hay en una cadena
function M.contarPalabras(cadena)
    if not cadena or #cadena == 0 then
        return 0
    end
    
    local contador = 0
    -- Separamos por espacios
    for palabra in string.gmatch(cadena, "%S+") do
        contador = contador + 1
    end
    return contador
end

-- Invierte una cadena
function M.invertir(cadena)
    return string.reverse(cadena)
end

-- Verifica si una cadena está vacía o solo tiene espacios
function M.estaVacia(cadena)
    if not cadena then
        return true
    end
    -- Eliminamos espacios y verificamos si queda algo
    local sinEspacios = string.gsub(cadena, "%s+", "")
    return #sinEspacios == 0
end

-- Repite una cadena n veces
function M.repetir(cadena, veces)
    if not cadena or veces < 1 then
        return ""
    end
    
    local resultado = ""
    for i = 1, veces do
        resultado = resultado .. cadena
    end
    return resultado
end

return M

Ahora podemos usar este módulo en nuestro programa:

-- main.lua
local texto = require("texto")

local frase = "hola mundo"
print("Original:", frase)
print("Capitalizada:", texto.capitalizar(frase))
print("Palabras:", texto.contarPalabras(frase))
print("Invertida:", texto.invertir(frase))
print()

local mensaje = "   "
print("¿Está vacía?:", texto.estaVacia(mensaje))
print()

print("Repetir:", texto.repetir("Lua ", 5))

Resultado:

Original: hola mundo
Capitalizada: Hola mundo
Palabras: 2
Invertida: odnum aloh

¿Está vacía?: true

Repetir: Lua Lua Lua Lua Lua 

Organizando módulos en carpetas

A medida que tu proyecto crece, querrás organizar tus módulos en carpetas. Aquí te muestro una estructura típica:

mi_proyecto/
├── main.lua
├── modulos/
│   ├── matematicas.lua
│   ├── texto.lua
│   └── utilidades.lua
└── config.lua

Para cargar un módulo que está en una carpeta, usa un punto (.) en lugar de una barra (/):

-- Si matematicas.lua está en la carpeta 'modulos'
local mat = require("modulos.matematicas")

Esta es la forma estándar en Lua de trabajar con módulos en subcarpetas.

Ejercicios prácticos

Ejercicio 1: Módulo de conversiones

Crea un módulo llamado conversiones.lua que incluya funciones para:

  • Convertir de kilómetros a millas
  • Convertir de millas a kilómetros
  • Convertir de Celsius a Fahrenheit
  • Convertir de Fahrenheit a Celsius

Luego crea un programa que use este módulo para realizar varias conversiones.

Pista: 1 kilómetro = 0.621371 millas, y la fórmula para convertir Celsius a Fahrenheit es: F = (C * 9/5) + 32

Ejercicio 2: Módulo de validación

Crea un módulo llamado validacion.lua que contenga funciones para validar:

  • Si una cadena es un email válido (contiene @ y un punto después del @)
  • Si un número está en un rango específico
  • Si una cadena tiene una longitud mínima y máxima
  • Si una cadena contiene solo letras

Crea un programa que pruebe cada una de estas funciones con diferentes valores.

Ejercicio 3: Calculadora modular

Crea un programa calculadora que use módulos:

  1. Un módulo operaciones_basicas.lua con suma, resta, multiplicación y división
  2. Un módulo operaciones_avanzadas.lua con potencia, raíz cuadrada y porcentaje
  3. Un programa principal que presente un menú al usuario y le permita elegir qué operación realizar

Errores comunes y cómo evitarlos

Error 1: Olvidar el return

Si olvidas poner return M al final de tu módulo, require devolverá true en lugar de tu tabla de funciones:

-- matematicas.lua (INCORRECTO)
local M = {}

function M.sumar(a, b)
    return a + b
end

-- ¡Falta el return M!
-- main.lua
local mat = require("matematicas")
print(mat.sumar(5, 3))  -- Error: mat es true, no tiene función sumar

Solución: Siempre termina tu módulo con return M.

Error 2: Archivo no encontrado

Si Lua no encuentra tu módulo, obtendrás un error como:

module 'matematicas' not found

Causas comunes:

  • El archivo no está en la misma carpeta que tu programa principal
  • El nombre en require no coincide con el nombre del archivo
  • Olvidaste la extensión .lua en el nombre del archivo (debe ser matematicas.lua, no matematicas)

Solución: Verifica que el archivo existe, está en el lugar correcto y el nombre coincide exactamente.

Error 3: Crear variables globales accidentalmente

Si olvidas usar local en tu módulo, las variables se vuelven globales:

-- matematicas.lua (INCORRECTO)
M = {}  -- ¡Sin local! Ahora M es global

function M.sumar(a, b)
    return a + b
end

return M

Esto puede causar problemas si diferentes módulos usan el mismo nombre de variable.

Solución: Siempre usa local para las variables en tus módulos.

Buenas prácticas

Al crear módulos, sigue estas recomendaciones:

  1. Usa nombres descriptivos: El nombre del módulo debe indicar qué hace. matematicas.lua es mejor que mod1.lua.

  2. Una responsabilidad por módulo: Cada módulo debe hacer una cosa específica. No mezcles funciones de texto con funciones matemáticas en el mismo módulo.

  3. Documenta tus funciones: Incluye comentarios que expliquen qué hace cada función, qué parámetros recibe y qué devuelve.

  4. Valida los parámetros: Verifica que los valores recibidos son del tipo correcto antes de usarlos.

  5. Maneja los errores: Cuando algo puede salir mal (como dividir por cero), devuelve nil y un mensaje de error descriptivo.

  6. Usa local para todo: Tanto la tabla del módulo como las funciones privadas deben ser locales.

Resumen

En este artículo has aprendido:

  • Los módulos son archivos de Lua que contienen funciones y variables reutilizables
  • Para crear un módulo, devuelves una tabla con las funciones que quieres compartir
  • La función require se usa para cargar módulos en tu programa
  • Las funciones locales son privadas y solo se pueden usar dentro del módulo
  • Organizar tu código en módulos hace que sea más fácil de mantener y reutilizar
  • Seguir buenas prácticas te ayuda a crear módulos de calidad

Los módulos son una herramienta fundamental en Lua. A medida que practiques más, verás que hacen que tus programas sean mucho más organizados y fáciles de trabajar. En el siguiente artículo, aprenderás sobre el manejo básico de errores en Lua, lo que complementará muy bien lo que has aprendido sobre módulos.