Los atributos de módulo en Elixir son una herramienta muy versátil que funciona como anotaciones o metadatos en forma de clave-valor, definidos en tiempo de compilación. Estos atributos permiten almacenar información que el compilador extrae para distintos propósitos, como generar documentación, definir tipos o manejar configuraciones internas del módulo.
Un ejemplo claro que ya conocemos es el uso de @doc y @moduledoc, que sirven para documentar funciones y módulos respectivamente. Estas anotaciones son procesadas por herramientas que generan documentación automáticamente. Pero más allá de eso, existen otros atributos que podemos usar para diferentes fines, algunos heredados del mundo de Erlang, como @vsn, que indica la versión del módulo. Este atributo es útil para controlar versiones y evitar incompatibilidades cuando se actualiza el código, aunque hay que tener cuidado de no romper dependencias al cambiarlo.
Además, podemos definir nuestros propios atributos personalizados. Por ejemplo, si declaramos @mundo "world", el compilador tratará @mundo como una constante cuyo valor es "world". Esto significa que en cualquier parte del módulo donde usemos @mundo, el compilador reemplazará esa referencia por el valor literal en tiempo de compilación. Así, si cambiamos el valor a 5, todas las referencias a @mundo pasarán a ser 5 sin necesidad de modificar el código en múltiples lugares.
Este mecanismo es muy útil para definir constantes o parámetros de configuración que queremos mantener centralizados y evitar duplicaciones. Por ejemplo, podemos crear un atributo @mapa que contenga un mapa con configuraciones como host, puerto, usuario y contraseña:
@mapa %{
host: "localhost",
port: 3999,
user: "admin",
password: "admin"
}
Luego, en cualquier función del módulo, podemos usar @mapa para acceder a esta configuración. Sin embargo, es importante entender que el compilador no hace un simple copia y pega del valor en el código generado. En realidad, cuando definimos una función que devuelve esta configuración, el código resultante en la máquina virtual BEAM es una función que retorna el mapa, y las referencias a @mapa se traducen en llamadas a esa función. Esto implica que la semántica de usar un atributo como constante no es exactamente igual a definir una función que devuelve un valor constante, aunque en la práctica puede ser muy similar.
Otra característica interesante de los atributos de módulo es que pueden actualizarse durante la compilación. Por ejemplo, podemos definir un atributo @lista como una lista vacía inicialmente:
@lista []
Luego, en otra parte del módulo, podemos actualizarlo agregando elementos:
@lista [2 | @lista]
Esto hace que el valor de @lista pase a ser [2]. Podemos comprobarlo con funciones que impriman el valor de @lista en distintos momentos, y veremos cómo cambia conforme se actualiza. Esta capacidad de modificar atributos durante la compilación es un truco muy utilizado en librerías para construir listas o colecciones de datos en tiempo de compilación, aunque entra en el terreno de la metaprogramación, que es un tema más avanzado.
Un caso práctico de esta actualización es el funcionamiento interno de @doc. Cada vez que usamos @doc para documentar una función, en realidad estamos sobrescribiendo el valor del atributo para asociar la documentación correcta a cada función. El compilador, mediante cierta magia interna, logra enlazar cada documentación con su función correspondiente, lo que es fundamental para herramientas que extraen documentación automáticamente.
En resumen, los atributos de módulo en Elixir son anotaciones que nos permiten almacenar metadatos, constantes o configuraciones, y que el compilador procesa para distintos fines. Podemos definir nuestros propios atributos, usarlos como constantes, actualizarlos durante la compilación y aprovecharlos para mejorar la organización y claridad de nuestro código. Aunque su uso puede parecer sencillo, detrás hay detalles importantes sobre cómo el compilador los maneja y cómo afectan la semántica del código.