Cuando uno viene de programar en C#, piensa que utilizar código de otros proyectos o namespaces
será igual de sencillo en todos los lenguajes. Pero resulta que hay lenguajes como Elixir en los que la cosa no es tan sencilla y existen varias maneras de compartir código entre módulos. Sin duda es una de las cosas que más me ha costando pillar (igual es que soy un zote), así que he pensado en escribirlo para aclararme yo, y de paso aclarar a quién pueda leerlo. Ojo, no es que sea difícil, pero hay que tener en cuenta que en Elixir existen las macros
y que no existe el concepto de namespace
. Así que hay más métodos para importar que el clásico using
de C#.
Alias
Esta es sin duda la directiva más fácil de entender. Aunque no se utiliza para compartir código, si puede simplificar mucho lo que escribamos. En Elixir, coomo he dicho antes, no existen namespaces
, pero se pueden prefijar los módulos para tenerlos organizados. Imaginando el siguiente módulo:
defmodule Console.Writer do
def write_line(text) do
IO.puts(text)
end
end
El módulo es sencillo de entender. Cuando desde otro módulo, queramos escribir algo en la consola, solo tendremos que escribir Console.Writer.write_line "mensaje en pantalla"
. Si solo lo tenemos que escribir una vez, no hay problema. ¿Y si lo utilizamos muchas veces? Entonces es cuando alias
nos puede servir de ayuda. Por ejemplo si al principio de nuestro módulo, escribimos lo siguiente:
alias Console.Writer
Nos bastará con poner Writer.write_line "mensaje en palntalla"
, para hacerlo funcionar. Pero si todavía queremos reducir más, o poner otro nombre diferente podemos hacer esto:
alias Console.Writer as: console
Ahora usar nuestro Writer
será tan sencillo como escribir console.write_line "mensaje en pantalla"
Import
Con import
podemos hacer algo parecido a alias
, pero en este caso además importamos las funciones para que podamos utilizarlas directamente en nuestros módulos. Se ve mejor con un ejemplo. Si abrimos iex
y directamente tratamos de usar la librería Enum
, pasará esto:
iex(1)> sort [2, 1, 5]
** (CompileError) iex:1: undefined function sort/1
Si hacemos Enum.sort [2, 1, 4]
el código funcionará, pero como vemos estamos obligados a escribir el nombre completo. En cambio con un import
al principio funcionará sin problemas:
iex(1)> import Enum
Enum
iex(2)> sort [2,1,5]
[1, 2, 5]
Ahora ya no hace falta que escribamos toda la ruta de las funciones que hay en Enum
(como sort
), ya que estas funciones están disponibles en nuestro módulo, como si las hubiéramos programado nosotros.
Con import
también podemos indicar qué funciones queremos importar, evitando cargar todas.
import Integer only: :macros #importa solo macros de la librería Integer
import Integer only: :functions #importa solo las funciones de la librería Integer
import Enum only: [sort:1] #importa solo la función sort con "arity" 2 (que recibe dos parámetros) de la librería Enum
Require
En Elixir podemos utilizar macros
para ampliar el lenguaje. De hecho Elixir está lleno de macros que podremos usar y aquí es cuando entra la directiva require
.
Para que una macro funcione al usarla desde nuestros módulos, le tendremos que decir al compilador que necesitamos tenerla lista y compilada, antes de ejecutar nuestro código. Por ejemplo para utilizar la macro is_even
de la librería Integer, antes tendremos que hacer un require
.
defmodule Enteros do
require Integer
def es_par(num) do
Integer.is_even(num)
end
end
Si no lo hacemos Elixir nos devolverá el error “you must require Integer before invoking the macro Integer.is_even/1”. En definitiva, para usar macros, nos tenemos que asegurar de que están cargadas antes de utilziarlas, y esto lo hacemos con require
.
Use
En este caso use
se trata de una macro, que está relacionada con require
. Si utilizamos use
en un módulo, nos aseguramos de primero hacer un require
y luego llamar a la función __using__
para inicializar algún tipo de estado o importar librerías asociadas. Esta función es una especie de constructor, salvando las distancias.
Un caso típico es el del framework de test que podemos usar en Elixir. Así, cuando hacemos esto:
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
El compilador acaba traduciéndolo en esto:
defmodule AssertionTest do
require ExUnit.Case
ExUnit.Case.__using__([async: true])
test "always pass" do
assert true
end
end
La función __using__
la podremos definir en cualquiera de nuestros módulos para poder llamarla al utilizar use
.
Conclusión
En definitiva, Elixir tiene varias formas diferentes de compartir código entre módulos, y es improtante que las conozcamos todas. Aunque algunas como alias
e import
son sencillas de entender, use
y require
son algo más complejas, ya que implican la utilización de macros. Y espero poder hablaros de macros en un futuro no muy lejano.
¿Quiéres que te avisemos cuando se publiquen nuevas entradas en el blog?
Suscríbete por correo electrónico o por RSS