Es ya una costumbre recibir la noticia de un nuevo lanzamiento de Erlang durante la CodeBEAM de Estocolmo. En este caso no fue menos y ese mismo día se hizo el lanzamiento de OTP 25.0 incluyendo una nueva característica para el lenguaje y la posibilidad de agregar nuevas a modo experimental, ¿quieres saber qué trae la nueva OTP 25?
Podemos leer los highlights de Kenneth Lundin, estos fueron publicados el 18 de mayo de 2022, un día antes de salir al escenario a presentar durante la conferencia el lanzamiento de Erlang/OTP 25.0. Abriré una sección por cada una de las secciones de los highlights y no solo explicaré en qué consiste cada mejora, sino también daré mi opinión.
Nuevas funciones para maps
y lists
Aunque finalmente no pudo ser tener lists:transpose/1
, sí que hay otras funciones que pudieron ser incorporadas en el código fuente de Erlang, entre ellas:
-
maps:groups_from_list/2,3
, basada en el resultado de la primera función aplicada sobre cada elemento de la lista pasada como segundo parámetro, esta función genera un mapa donde la clave es el resultado de llamar a la función anónima del primer parámetro y el valor es la lista de elementos que casan con la clave:
maps:groups_from_list(fun(X) -> X rem 2 end, [1, 2, 3])
% #{0 => [2], 1 => [1, 3]}
maps:groups_from_list(fun erlang:length/1, ["ant", "buffalo", "cat", "dingo"])
% #{3 => ["ant", "cat"], 5 => ["dingo"], 7 => ["buffalo"]}
maps:groups_from_list(fun(X) -> X rem 2 end, fun(X) -> X * X end, [1, 2, 3])
% #{0 => [4], 1 => [1, 9]}
-
lists:enumerate/1,2
toma una lista de elementos convirtiéndolos en una tupla donde el primer elemento será un contador que se irá incrementando, opcionalmente como primer parámetro podemos indicar por que número comience a contar:
lists:enumerate([a, b, c])
% [{1, a}, {2, b}, {3, c}]
lists:enumerate(10, [a, b, c])
% [{10, a}, {11, b}, {12, c}]
-
lists:uniq/1,2
elimina los elementos duplicados preservando el orden de los elementos. Como primer parámetro y opcionalmente podemos indicar una función anónima para modificar el elemento de la lista y así indicar los valores únicos basados en el retorno de esa función:
lists:uniq([3, 3, 1, 2, 1, 2, 3])
% [3, 1, 2]
lists:uniq([a, a, 1, b, 2, a, 3])
% [a, 1, b, 2, 3]
lists:uniq(fun({X, _}) -> X end, [{b, 2}, {a, 1}, {c, 3}, {a, 2}])
% [{b, 2}, {a, 1}, {c, 3}]
Características seleccionables y nueva expresión maybe
Una de las grandes adiciones de Erlang/OTP 25.0 ha sido la nueva expresión maybe
, influenciada de lenguajes como Elixir (donde es llamada with
), esta expresión nos permite cambiar códigos como el siguiente:
commit_write(OpaqueData) ->
B = OpaqueData,
case disk_log:sync(B#backup.file_desc) of
ok ->
case disk_log:close(B#backup.file_desc) of
ok ->
case file:rename(B#backup.tmp_file, B#backup.file) of
ok ->
{ok, B#backup.file};
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end.
Este código, en versiones anteriores se hace difícil escribirlo de otra forma diferente, con la nueva estructura de maybe
es posible reescribirlo así:
commit_write(OpaqueData) ->
maybe
ok ?= disk_log:sync(OpaqueData#backup.file_desc),
ok ?= disk_log:close(OpaqueData#backup.file_desc),
ok ?= file:rename(OpaqueData#backup.tmp_file, OpaqueData#backup.file),
{ok, OpaqueData#backup.file}
end.
Mientras cada ejecución concuerde con el valor a la izquierda del operador ?=
se siguen evaluando funciones, en caso de no haber concordancia, entonces ese valor se retornaría parando el flujo de ejecución.
En verdad, los valores no concordantes pueden ser enviados a otro bloque inferior tras un else
donde si concuerdan, se ejecute un código específico, volviendo al código anterior:
commit_write(OpaqueData) ->
maybe
ok ?= disk_log:sync(OpaqueData#backup.file_desc),
ok ?= disk_log:close(OpaqueData#backup.file_desc),
ok ?= file:rename(OpaqueData#backup.tmp_file, OpaqueData#backup.file),
{ok, OpaqueData#backup.file}
else
{error, Reason} -> {error, Reason}
end.
Para más información puedes echarle un vistazo al EPP-49.
Como decíamos al principio, esta es una nueva característica y como tal puede romper código antiguo al reservar el átomo maybe
para un uso específico. Por eso, en Erlang/OTP 25.0 esta expresión no está disponible por defecto, debemos agregar en el módulo donde queramos emplearla la siguiente línea de preprocesador:
-feature(maybe_expr, enable).
Como te puedes figurar, al configurar enable
es cuando la habilitamos y si agregamos otra directiva donde pongamos disable
la deshabilitaríamos. Por otro lado, es posible agregar esta directiva directamente en el compilador o incluso en rebar3
para ser aplicado a todo el proyecto. En la línea de comandos sería:
erl -enable-feature maybe_expr
Mi opinión al respecto es que me parece un gran avance. Al igual que lo fue la adición de los mapas como nuevo tipo de dato en Erlang, agregar maybe
y la posibilidad de agregar nuevas características opcionales en el sistema me parece un gran avance para el lenguaje y su plataforma.
Dialyzer
Uno de los elementos más mencionado en las charlas de Code BEAM EU 2022 fue Dialyzer. Parece que vuelve a estar de moda. No en vano, ya que es una herramienta de análisis de código muy útil tanto para Erlang como para Elixir.
En este nuevo lanzamiento de Erlang han mejorado significativamente los tipos de datos. Han agregado las opciones de missing_return
y extra_return
haciendo que funciones como min/2
, max/2
o erlang:raise/3
las entienda mejor generando mejores avisos para el programador.
Mejoras en JIT
Como dije en un artículo anterior sobre Erlang/OTP 24 donde anuncié además la 3ª edición de Erlang/OTP: Un Mundo Concurrente, el lanzamiento de JIT fue uno de los grandes hitos para acercar un mejor rendimiento a la máquina virtual de Erlang y estas mejoras han seguido aumentando. En esta versión se ha agregado soporte para las arquitecturas AArch64 (ARM64) usada por Apple para sus nuevos microprocesadores M1 y los nuevos dispositivos de Raspberry Pi.
Además se ha mejorado la generación de tipos proporcionados por el compilador de Erlang y el soporte para las herramientas perf
y gdb
que permiten realizar desde la terminal del sistema operativo un análisis de rendimiento y depurar el código respectivamente.
Una de las pruebas realizadas para comprobar el incremento de rendimiento ha sido la ejecución de Dialyzer para generar un PLT (de esto hablo en un capítulo detallado en Erlang/OTP Volumen II: Las Bases de OTP) que tomaba en la versión anterior 18,38 segundos en la nueva versión tomó 9,64 segundos, ¡casi la mitad!
Otras pruebas para ver el rendimiento con JIT activo o no se hicieron con la ejecución de base64
obteniendo con JIT activo:
fun base64:encode/1: 1000 iterations in 11846 ms: 84 it/sec
fun base64:decode/1: 1000 iterations in 14617 ms: 68 it/sec
Sin JIT activo:
fun base64:encode/1: 1000 iterations in 25938 ms: 38 it/sec
fun base64:decode/1: 1000 iterations in 20603 ms: 48 it/sec
Podemos ver una mejora increíble en la codificación que pasa de 38 iteraciones/segundo a 84, consiguiendo realizar más del doble de ejecuciones y para la decodificación pasa de 48 iteraciones/segundo a 68, no es un dato tan increíble como el primero pero igualmente muy bueno.
Facilidades para el programador
Otro de los focos que intentan mantener al lanzar cada nueva versión es intentar mejorar más la interacción con los programadores. Hacer la vida del programador más fácil. Recuerdo cuando la versión R13 incorporó el volcado de pila donde se encontraba el error, fue todo un hito casi tan impresionante como cuando R15 nos comenzó a mostrar el número de línea del error. A día de hoy no solo nos muestra la línea sino también la columna y en este nuevo lanzamiento además nos da más información sobre el fallo cometido.
Por ejemplo, en este código de construcción de un binario:
bin(A, B, C, D) ->
<<A/float, B:4/binary, C:16, D/binary>>.
Si ejecutamos el código en Erlang/OTP 24 obtendremos un error:
bin(<<"abc">>, 2.0, 42, <<1:7>>)
% ** exception error: bad argument
% in function t:bin/4 (t.erl, line 5)
El error nos dice que algo sucede pero no nos da pistas de qué puede ser exactamente lo que esté fallando. En la nueva versión de Erlang/OTP 25 ahora sí obtenemos un error más fácil de seguir:
bin(<<"abc">>, 2.0, 42, <<1:7>>)
% ** exception error: construction of binary failed
% in function t:bin/4 (t.erl, line 5)
% *** segment 1 of type 'float': expected a float or an integer but got: <<"abc">>
Ahora nos informa con claridad el tipo de fallo construction of binary failed
y exactamente el segmento que falló (<<"abc">>
) y el porqué (segment 1 of type 'float'
). Además, no solo nos da información del tipo, si escribimos el siguiente código:
bin(2.0, <<"abc">>, 42, <<1:7>>)
% ** exception error: construction of binary failed
% in function t:bin/4 (t.erl, line 5)
% *** segment 2 of type 'binary': the value <<"abc">> is shorter than the size of the segment
Nos avisa de que el tipo es binary
, lo cual está bien, pero a continuación nos dice que el valor <<"abc">>
es más corto que el tamaño del segmento que se espera. En la función indicamos un segmento B:4/binary
.
Otro paso adelante en la buena dirección para hacer Erlang mucho más comprensible para los que llegan nuevos al lenguaje.
Otras mejoras
Hay otras mejoras que se mencionan en los highlights como las mejoras relativas a la escritura concurrente en las tablas ETS que incorporan un sistema de contadores descentralizados, mejoras para instalaciones en Android, mejoras en los errores producidos para los registros, el parámetro short
cuando convertimos números de coma flotante a formato texto, el cambio del módulo slave
por peer
, una nueva retrollamada format_status/1
para los comportamientos (próximamente en Erlang/OTP Volumen II), mejoras para hacer más eficiente el módulo timer
, actualizaciones en crypto
(comentado en Erlang/OTP Volumen I) y un nuevo generador de números pseudo-aleatorios (rand:mwc59
).
Como ves, mucho trabajo realizado por la comunidad y el equipo que mantiene Erlang.
¿Qué te parecen las mejoras que ha mostrado Erlang/OTP en esta nueva versión? ¿Crees que se implementará el operador pipe para el siguiente lanzamiento? ¡Déjanos tu comentario!