Notxor tiene un blog

Defenestrando la vida


Aprendiendo erlang durante la cuarentena

Durante estos días de encierro forzoso en casa y sin más obligaciones que distraer la mente en algo para que el techo no nos caiga encima sin haberse movido, decidí aprender otro lenguaje funcional.

Mi primera intención fue volver a intentarlo con Haskell, un lenguaje que viene resistiéndose desde hace tiempo, pero me encontré que cuando fui a instalarlo en mi sistema, pedía 1,8Gb de espacio... y me pareció una exageración. Caber cabía, pero se me hace mucho desperdicio para un mero aprendizaje.

Descartado Haskell me acordé de erlang. Hace unos años ya le eché un ojo a ese lenguaje, pues hacia él me llevó la curiosidad de encontrar una magnífica herramienta creada con él: wings 3D, en una época en que le eché muchas horas al tema del 3D. En aquella época el código que yo escribía solía ser C/C++ o Java. Empezaba a trastear con Python y me divertía con Smalltalk, e incluso durante un año y medio en el que trabajé como programador con Delphi/Pascal, nunca había tratado con un lenguaje de los llamados funcionales. Entonces, con aquellos conocimientos, o limitaciones ─dirán algunos─, erlang me pareció un lenguaje muy marciano y después de echarle un vistazo me olvidé de él.

Hace poco, salió un proyecto en el que podría colaborar y se realizaría en erlang, base de datos couchDB, ─otra de las herramientas punteras hechas con ese lenguaje─. El caso es que aquél proyecto no salió: se lo adjudicaron a alguna consultora más fuerte que cuatro animosos frikis. Comencé en aquellos días a repasar erlang pero cuando se torció el proyecto lo dejé de nuevo y casi me olvidé de su existencia.

Estos días, pensando en qué hacer y habiendo descartado las 1,8Gb que pedía Haskell, me acordé de nuevo de ese lenguaje: funcional, marciano y friki que promete muchas cosas buenas:

Es un lenguaje que, como se puede apreciar, promete muchas buenas cosas pero que, también supongo, me tropezará con algún problema o pega.

Un vistazo general

Sin ánimo de ser exhaustivo quiero compartir una visión general sobre el lenguaje y los pasos que he venido dando en mi proceso de aprendizaje. Lo primero que hice, como es de suponer es instalar erlang en mi ordenador:

sudo zypper install erlang

En otros sistemas, cada uno sabrá cómo hacerlo... pero lo que me llamó la atención es que iba a descargar algunas dependencias y que entre todo el tamaño de la descarga no superaba los 30Mb. Vale que una vez descomprimido el erlang en mi sistema ocupa unas 50Mb, pero no son la 1,8Gb que pedía Haskell sólo en la descarga, que instalado, sería aún más. Este es el primer punto a su favor, no es un sistema demasiado pesado. Tanto, que ya puestos, instalé algunas herramientas para el desarrollo, como la documentación o el debugger o el observer, que aún no sé utilizar pero que prometen ser útiles. Con esos paquetes se instalaron también otras dependencias y en total, entre unas cosas y otras, tengo ocupadas 98Mb de disco duro, que a estas alturas de la vida, tampoco son un gasto significativo. Otros lenguajes como Python, Ruby o Java ocupan bastante más que eso.

Un ejemplo sencillo

No es un lenguaje funcional puro según dice la misma documentación de erlang, sin embargo, es lo más funcional que he visto hasta ahora. De hecho, ni siquiera tiene instrucciones para hacer bucles; nada de while ni de for. Las tareas repetitivas se realizan mediante la recursión sobre listas, por ejemplo. Para no hablar en vacío, pongo un ejemplo sencillo, pero que muestra varias características del lenguaje. Obsérvese el siguiente código:

-module(factorial).
-export([factorial/1]).

factorial(1) ->
    1;
factorial(N) ->
    N * factorial(N-1).

Lo explicaré a continuación, pero vamos a ver cómo trabaja erlang compilando el código y ejecutándolo desde su herramienta interactiva de shell: erl.

$ erl
Erlang/OTP 22 [erts-10.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Eshell V10.7  (abort with ^G)
1> c(factorial).
{ok,factorial}
2> factorial:factorial(5).
120
3> factorial:factorial(-5).
Terminado (killed)
$ _

Vemos que al intentar calcular el factorial de un número negativo, llega un momento en que bloquea y arrastra consigo al prompt interactivo de erlang.

Si observamos el código vemos que los comandos de las primeras líneas comienzan por -. Podríamos considerarlos como la cabecera del módulo. En nuestro caso -module(factorial). lo que hace es definir el fichero de código como un módulo de nombre factorial. También es importante el . al final, pues es lo que delimita las expresiones de erlang: toda expresión en este lenguaje va a terminar con un punto. La segunda línea exporta una lista de funciones del módulo. En nuestro caso sólo tenemos una función factorial/1, pero podemos observar que aún así debemos delimitarlo como una lista [...]. La otra cosa que nos puede llamar la atención es la manera de referirse a una función: el nombre de la misma y separado con una barra el número de argumentos de la misma. Eso nos permite, por ejemplo, si tienen distinto número de parámetros, trabajar con dos funciones con el mismo nombre. Por último, tenemos la expresión que define la función: comienza con factorial(1) y acaba donde está el punto, tres líneas más abajo. Como vemos, está dividida en dos partes según el argumento recibido: si es un 1 devuelve 1 y si no, devuelve el resultado de multiplicar el argumento N por el factorial de N-1... es decir, la típica función recursiva llamándose a sí misma. Pero vemos, que las dos partes de la función están separadas por un carácter ;.

Entorno interactivo

El entorno interactivo que nos presenta erlang es erl, como hemos visto antes. Ahí podemos cargar los módulos que necesitemos y hacer muchas más cosas, pero vamos poco a poco.

En el ejemplo anterior hemos visto que compilábamos el módulo que habíamos creado mediante el comando c(factorial). antes de poder utilizarlo con el comando factorial:factorial(5). y que nos permite analizar cómo son las llamadas en este lenguaje. Para empezar es el nombre del módulo y de la función separado por el carácter :.

Pero de momento, vamos a centrarnos en el eshell:

$ erl
Erlang/OTP 22 [erts-10.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Eshell V10.7  (abort with ^G)
1> _

En la segunda línea nos dice abort with ^G, y puede parecer que C-g (en notación de emacs) lo que haga solamente es parar el proceso de Eshell, pero no es cierto. Si pulsamos esa combinación de teclas nos aparecerá un prompt tal que -->:

$ erl
Erlang/OTP 22 [erts-10.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Eshell V10.7  (abort with ^G)
1> 
User switch command
 --> _

Antes del prompt vemos el mensaje User switch command. Si pulsamos h, o ?, y enter nos mostrará una lista de posibles comandos:

1> 
User switch command
 --> h
  c [nn]            - connect to job
  i [nn]            - interrupt job
  k [nn]            - kill job
  j                 - list all jobs
  s [shell]         - start local shell
  r [node [shell]]  - start remote shell
  q                 - quit erlang
  ? | h             - this message
 --> _

Vamos a utilizar j y enter para ver la lista de trabajos, aunque de momento sólo tenemos nuestro eshell funcionando, así que contestará algo parecido a:

--> j
  1* {shell,start,[init]}
--> _

Y si tenemos nuestra consola ocupada con cálculos complejos y tarda en contestar, por poner un ejemplo, podemos necesitar otra consola, la lanzaremos con s... paralelismo desde la propia línea de comandos:

--> s
--> j
  1  {shell,start,[init]}
  2* {shell,start,[]}
--> _

Bien, vemos que ahora mismo tenemos 2 procesos shell corriendo y estamos enlazados al 2, porque está marcado con el *. Si queremos volver a la consola anterior utilizaremos el comando c 1. Si utilizamos sólo la c conectaremos con la shell a la que estemos enganchados. También, como se puede ver en el listado de comandos se pueden hacer más cosas, como matar un proceso (k), levantar un «nodo» para conexión remota (r), o salir del shell (q).

Un poco de programación defensiva

Como vimos antes, el calcular el factorial de un número negativo producía un bloqueo. Vamos a proteger un poco esa ejecución:

-module(factorial).
-export([factorial/1]).

factorial(1) ->
    1;
factorial(N) when N > 0 ->
    N * factorial(N-1).

El resultado será el siguiente:

$ erl
Erlang/OTP 22 [erts-10.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Eshell V10.7  (abort with ^G)
1> c(factorial).
{ok,factorial}
2> factorial:factorial(5).
120
3> factorial:factorial(-5).
** exception error: no function clause matching factorial:factorial(-5) (factorial.erl, line 4)
4> _

Como vemos la cláusula when establece que se haga el cálculo cuando N > 0, es decir, cuando es un número positivo y cuando es negativo se lanza una excepción porque no hay ninguna cláusula que tenga en cuenta ese caso. Sin embargo, esa excepción no se lleva consigo la ejecución del shell, lo cual es de agradecer y entraría en la filosofía de erlang que dice eso de déjalo que falle.

Tipos de datos

Están los típicos valores numéricos: enteros y de coma flotante que se ven limitados al máximo que pueda determinar la máquina, pero luego hay otros tipos que datos que vamos a ver resumidos.

Los primeros son los átomos o atoms, que algunos vienen definidos en el propio lenguaje, como son true, false, undefined, ok, error... Sin embargo, podemos definir los nuestros propios según los necesitemos.

En los números cabe destacar que podemos trabajar en distintas bases, como en otros lenguajes, pero en este caso la base se define con el operador # y no sólo soporta las clásicas bases 2, 8 y 16. Se pueden especificar bases entre 2 y 32. con la notación siguiente:

7> 2#1011.
11
8> 8#123.
83
9> 30#12a0e.
873014
10> 

Como en todos los lenguajes funcionales, las listas son el tipo de datos. En erlang ocurre lo mismo, sin embargo, las listas vienen cargadas con otro punto importante a tener en cuenta, por ejemplo:

1> [104, 111, 108, 97].
"hola"
2> _

¿Eh? ¿Cómo?... Pues eso, si es una lista de números y todos los números equivalen a caracteres imprimibles, la lista y la cadena unifican criterios. Sin embargo:

1> [104, 111, 108, 97].
"hola"
2> [104, 111, 108, 97, 0].
[104,111,108,97,0].
3> _

Cuando se encuentra con algún valor que no pertenece a ningún carácter imprimible, como es el 0, la lista se comporta como en cualquier otro lenguaje. Como vemos se definen con los caracteres [] y se separan por comas. Si en lugar de los corchetes utilizamos las llaves {}, lo que estamos definiendo es una tupla, que básicamente es una lista con características especiales. Por ejemplo, cuando compilábamos en el punto anterior el código del módulo factorial el resultado que nos devolvía era {ok,factorial}. Es decir, una tupla que contenía dos atoms uno que nos decía que todo ha ido bien en la compilación (ok) y otro con el nombre del módulo. Esto es algo muy habitual en erlang, que el retorno de una función sea un tipo compuesto que proporciona mucha más información que un simple valor numérico.

Además podemos definir nuestros propios datos gracias a los records. Es decir, podemos definir un tipo tal que:

-record(listin, {nombre, apellido1, apellido2, numero}).

Desde la shell la sintaxis se reduce y en lugar de la forma -record se utiliza la función rd:

rd(listin, {nombre, apellido1, apellido2, numero}).

Por ejemplo:

1> rd(listin, {nombre, apellido1, apellido2, telefono}).     
listin
2> A = {listin, "Fulanito", "de Tal", "y Tal", 987654321}.
#listin{nombre = "Fulanito",apellido1 = "de Tal",
                   apellido2 = "y Tal",telefono = 987654321}
3> A#listin.nombre.
"Fulanito"
4> _

Vemos que se utiliza la forma general de variable#registro.campo para acceder a un dato determinado.

He leído por encima, que la instalación básica de erlang proporciona una base de datos llamada Mnesia, que aún no sé cómo funciona pero que supongo que será toda una ayuda en aplicaciones grandes. Y tampoco he tocado el tema de conexión a través de red.

Conclusiones

De momento, el lenguaje erlang me está sorprendiendo y gustando. Ahora mismo estoy en plena fase de aprendizaje, apenas he leído un poco y más bien por encima, un libro para hacerme una idea general de cómo funciona. Me he encontrado una sintaxis bastante simple, no hay que aprenderse largos listados de comandos ─con sus excepciones─ que nos compliquen la vida. Es un lenguaje expresivo y aunque de primeras, si vienes acostumbrado de otros lenguajes, puede parecer un poco marciano, como ya dije antes que me pasó a mí, pero con muy poco la práctica hace que todo el código sea claro y fácil de entender.

Puesto en el camino, y para aprender realmente el lenguaje hay que utilizarlo, así que estoy pensando alguna aplicación que utilice las características del lenguaje con profusión y poner a prueba esa capacidad de resistencia a fallos, recambio en caliente, concurrencia, etc. Se me había ocurrido alguna especie de MUD, aunque aún no tengo muy claro qué o cómo... en estos días de encierro forzoso espero delinear un poco mejor la idea y ya veremos si la puedo poner en marcha. De momento estoy utilizando algunos libros que he encontrado por ahí para aprender cuantos más detalles mejor, que aún me falta un poco para dar el salto hacia un proyecto.


Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.