Programación · Lenguajes de programación
Tutorial de Erlang para principiantes
Respuesta corta
Erlang es un lenguaje funcional y concurrente creado en Ericsson para sistemas que deben estar siempre activos, como centrales telefónicas y servidores de mensajería. Los programas se ejecutan en la máquina virtual BEAM como muchos procesos ligeros que se comunican mediante mensajes y que los supervisores reinician cuando fallan. Esta página cubre la compilación de módulos, los tipos, la coincidencia de patrones, la recursión, las listas y mapas, los procesos, OTP y el manejo de errores.
Qué es Erlang y para qué se usa
Erlang es un lenguaje funcional y de tipado dinámico diseñado en Ericsson en la década de 1980 para centrales telefónicas que debían funcionar sin detenerse. Se publicó como código abierto en 1998. Los programas de Erlang se ejecutan en la máquina virtual BEAM como muchos procesos ligeros que no comparten nada y se comunican mediante mensajes, e incluyen OTP (Open Telecom Platform), un conjunto de bibliotecas y principios de diseño para construir sistemas tolerantes a fallos.
Erlang se usa en equipos de telecomunicaciones, plataformas de mensajería (el backend de WhatsApp se construyó, como es bien sabido, sobre Erlang), brokers de mensajes como RabbitMQ, bases de datos como Riak y CouchDB, y sistemas de pagos e IoT. Elixir y Gleam también se ejecutan en BEAM y pueden usar directamente las bibliotecas de Erlang.
Instala Erlang y ejecuta Hola, mundo
Instala Erlang/OTP desde erlang.org o con un gestor de paquetes (por ejemplo brew install erlang o apt install erlang). El código vive en módulos; cada archivo name.erl define el módulo name y lista las funciones que exporta.
% hello.erl
-module(hello).
-export([main/0]).
main() ->
io:format("Hello, world!~n").erlc hello.erl # compiles to hello.beam
erl -noshell -s hello main -s init stop
# Hello, world!
erl # interactive shell; c(hello). then hello:main().Los comentarios empiezan con %. ~n en una cadena de formato es un salto de línea y ~p imprime cualquier término. En la shell, cada expresión termina con un punto.
Puntuación, variables y tipos básicos
La puntuación de Erlang es la mayor sorpresa para los principiantes: una coma separa expresiones, un punto y coma separa cláusulas y un punto termina una función o un comando de la shell. Las variables empiezan con una letra mayúscula o un guion bajo y solo pueden enlazarse una vez.
1> Name = "Erlang".
"Erlang"
2> Year = 1986, Next = Year + 1.
1987
3> Year = 2000.
** exception error: no match of right hand side value 2000
4> 10 / 4.
2.5
5> {10 div 4, 10 rem 4}.
{2,2}
6> is_atom(ok).
true
7> <<"binary string">>.
<<"binary string">>| Tipo | Ejemplo | Notas |
|---|---|---|
| integer | 42, 16#FF | precisión arbitraria; 16# para hexadecimal |
| float | 3.14 | doble precisión de 64 bits |
| atom | ok, error, 'Hello' | constantes con nombre, en minúsculas o entre comillas |
| tuple | {ok, 42} | grupo de valores de tamaño fijo |
| list | [1, 2, 3] | lista enlazada; las cadenas como "abc" son listas de enteros |
| map | #{name => "Ann"} | almacén clave-valor |
| binary | <<"text">> | secuencia de bytes, la forma eficiente de almacenar texto |
true y false son átomos, no un tipo aparte. = es una coincidencia, no una asignación, y por eso la línea 3 falla: Year ya está enlazada a 1986.
Coincidencia de patrones, case y guardas
La coincidencia de patrones desempaqueta tuplas, listas y mapas y selecciona qué cláusula se ejecuta. Las guardas, escritas con when, añaden condiciones a una cláusula. case e if son expresiones que devuelven un valor.
-module(match).
-export([describe/1, grade/1, demo/0]).
describe({ok, Value}) -> io_lib:format("ok: ~p", [Value]);
describe({error, Reason}) -> io_lib:format("error: ~p", [Reason]);
describe(_Other) -> "unknown".
grade(Score) when Score >= 90 -> a;
grade(Score) when Score >= 70 -> b;
grade(_) -> c.
demo() ->
[First | Rest] = [1, 2, 3],
#{name := Name} = #{name => "Ann", age => 31},
Size = case length(Rest) of
0 -> empty;
N when N < 5 -> small;
_ -> large
end,
io:format("~p ~p ~s ~p ~p~n", [First, Rest, Name, Size, grade(75)]),
io:format("~s~n", [describe({error, timeout})]).
% match:demo() prints:
% 1 [2,3] Ann small b
% error: timeoutTodas las cláusulas excepto la última terminan en ;, y la última termina en .. Si ninguna cláusula coincide, el proceso lanza un error function_clause o case_clause.
Funciones, recursión y funciones de orden superior
Las funciones se identifican por su nombre y su aridad, escritos name/Arity. No hay bucles; la repetición usa recursión, y las funciones con recursión de cola se ejecutan en espacio de pila constante. Las funciones anónimas se escriben fun ... end.
-module(fns).
-export([factorial/1, sum/1, demo/0]).
factorial(0) -> 1;
factorial(N) when N > 0 -> N * factorial(N - 1).
sum(List) -> sum(List, 0). % public wrapper
sum([], Acc) -> Acc; % tail-recursive helper sum/2
sum([H | T], Acc) -> sum(T, Acc + H).
demo() ->
Double = fun(X) -> X * 2 end,
io:format("~p~n", [factorial(20)]), % 2432902008176640000
io:format("~p~n", [sum([1, 2, 3, 4])]), % 10
io:format("~p~n", [Double(21)]), % 42
io:format("~p~n", [lists:map(Double, [1, 2, 3])]), % [2,4,6]
io:format("~p~n", [[X * X || X <- [1, 2, 3, 4, 5], X rem 2 =:= 1]]). % [1,9,25]La última línea usa una lista por comprensión. Fíjate en los operadores de comparación: =:= y =/= comprueban igualdad y desigualdad exactas, == y /= comparan números por valor (así que 1 == 1.0 es verdadero), y menor o igual se escribe =<, no <=.
Listas, mapas y la biblioteca estándar
El módulo lists ofrece map, filter, foldl, sort, reverse y muchas más; maps trabaja con mapas; string y binary manejan texto. La referencia completa está en la documentación de OTP.
1> L = [5, 3, 8, 1].
[5,3,8,1]
2> lists:sort(L).
[1,3,5,8]
3> lists:filter(fun(X) -> X > 2 end, L).
[5,3,8]
4> lists:foldl(fun(X, Acc) -> X + Acc end, 0, L).
17
5> M = #{ann => 31, bo => 25}, map_size(M).
2
6> M2 = M#{cy => 40}, map_size(M2).
3
7> maps:get(bo, M2).
25
8> maps:get(zed, M2, none).
noneM#{cy => 40} crea un mapa nuevo; M en sí nunca cambia. Usa := en lugar de => para actualizar una clave que ya debe existir.
Procesos y paso de mensajes
Los procesos son la característica distintiva de Erlang. spawn inicia un proceso nuevo, ! envía un mensaje y receive espera uno, haciendo coincidir los mensajes por patrón. Los procesos son baratos, así que un sistema puede ejecutar muchísimos. Los enlaces (links) y los monitores permiten que un proceso se entere de que otro ha muerto, y los supervisores de OTP lo usan para reiniciar automáticamente los trabajadores que fallan.
-module(counter).
-export([start/0, loop/1]).
start() ->
Pid = spawn(counter, loop, [0]),
Pid ! {add, 5},
Pid ! {add, 10},
Pid ! {get, self()},
receive
{count, N} -> io:format("count = ~p~n", [N]) % count = 15
after 1000 ->
io:format("timeout~n")
end.
loop(Count) ->
receive
{add, N} -> loop(Count + N);
{get, From} -> From ! {count, Count}, loop(Count)
end.Este bucle escrito a mano es exactamente lo que generaliza el comportamiento gen_server de OTP. En el código de producción escribirías un módulo gen_server y lo colocarías bajo un supervisor.
Manejo de errores y «let it crash»
Erlang tiene tres clases de excepciones: error, throw y exit. Los fallos esperados normalmente se devuelven como tuplas {ok, Value} o {error, Reason}. Los inesperados muchas veces ni se capturan: el proceso falla y su supervisor lo reinicia en un estado correcto conocido. Cuando sí necesitas capturar, usa try ... catch.
1> file:read_file("missing.txt").
{error,enoent}
2> try 1 / 0 catch error:badarith -> division_failed end.
division_failed
3> try throw(my_reason) catch throw:R -> {caught, R} end.
{caught,my_reason}
4> case file:read_file("missing.txt") of {ok, Bin} -> Bin; {error, Why} -> Why end.
enoentUn programa pequeño y completo: frecuencia de palabras
Este módulo cuenta las palabras de una cadena con un mapa, las ordena por cantidad e imprime las tres más comunes. Combina funciones de cadenas, lists:foldl, mapas y una ordenación personalizada.
-module(wordfreq).
-export([top/2, main/0]).
top(Text, N) ->
Words = string:lexemes(string:lowercase(Text), " .,;:!?\n"),
Counts = lists:foldl(fun(W, Acc) ->
maps:update_with(W, fun(C) -> C + 1 end, 1, Acc)
end, #{}, Words),
Sorted = lists:sort(fun({W1, C1}, {W2, C2}) ->
{-C1, W1} =< {-C2, W2}
end, maps:to_list(Counts)),
lists:sublist(Sorted, N).
main() ->
Text = "The cat and the dog. The dog sat, and the cat ran.",
[io:format("~s: ~p~n", [W, C]) || {W, C} <- top(Text, 3)],
ok.
% the: 4
% and: 2
% cat: 2Siguientes pasos
- La documentación de Erlang/OTP en erlang.org, incluida la guía Getting Started y los OTP Design Principles.
- Learn You Some Erlang for Great Good!, un libro gratuito en línea.
- rebar3, la herramienta de compilación estándar, y Hex, el gestor de paquetes que comparte con Elixir.
- Las páginas de referencia de
gen_serverysupervisorcuando construyas servicios reales.
Preguntas frecuentes
¿Todavía se usa Erlang?
Sí. Erlang sigue haciendo funcionar sistemas de telecomunicaciones, servicios de mensajería y RabbitMQ, y el ecosistema de BEAM sigue creciendo gracias a Elixir y Gleam, que pueden usar las bibliotecas de Erlang.
¿Qué es OTP en Erlang?
OTP es un conjunto de bibliotecas, herramientas y principios de diseño que se distribuye con Erlang. Ofrece comportamientos (behaviours) como gen_server, supervisor y application que estructuran los programas tolerantes a fallos.
¿Debo aprender Erlang o Elixir?
Elixir tiene una sintaxis más amigable y un ecosistema web más grande, mientras que Erlang es más pequeño y es el lenguaje en el que están escritos OTP y muchas bibliotecas centrales. Ambos se ejecutan en la misma máquina virtual, y conocer uno hace que el otro sea fácil de leer.
¿Qué significa let it crash?
En lugar de tratar todos los errores posibles dentro de un proceso, dejas que el proceso falle y confías en que un supervisor lo reinicie en un estado limpio. El código de manejo de errores se mantiene pequeño y el sistema se recupera solo.
¿Por qué las variables de Erlang empiezan con mayúscula?
Erlang usa las mayúsculas para distinguir las variables de los átomos: Name es una variable, mientras que name es un átomo, un valor constante.