0% encontró este documento útil (0 votos)
9 vistas79 páginas

Introducción al Lenguaje Erlang

Este documento presenta el lenguaje de programación Erlang. Introduce conceptos como procesos, comunicación entre procesos mediante mensajes, concurrencia, funciones anónimas y listas intensionales. Explica características del lenguaje como la inmutabilidad de variables, representación de caracteres y cadenas como listas, y el uso de tuplas y ajuste de patrones.
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
9 vistas79 páginas

Introducción al Lenguaje Erlang

Este documento presenta el lenguaje de programación Erlang. Introduce conceptos como procesos, comunicación entre procesos mediante mensajes, concurrencia, funciones anónimas y listas intensionales. Explica características del lenguaje como la inmutabilidad de variables, representación de caracteres y cadenas como listas, y el uso de tuplas y ajuste de patrones.
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Erlang

Perdiendo el miedo a la programación concurrente

Manuel Montenegro
Facultad de Informática
1. Primeros pasos
2. Interacción entre procesos
3. Interacción entre nodos
4. Interacción entre máquinas
El lenguaje Erlang
● Desarrollado en 1986 por Ericsson.
● Inicialmente concebido para aplicaciones de telefonía.

Principales características
● Integra paradigmas funcional y concurrente.
● Dinámicamente tipado.
● Enfoque pragmático.
Principios de diseño
● Todo es un proceso.
● La creación y destrucción de procesos debe ser ligera y
eficiente.
● Los procesos no comparten recursos.
● La única manera de interacción entre procesos es el paso
de mensajes.
● Filosofía Let it crash.
1. Primeros pasos
Iniciar una shell
● Ejecuta el fichero single_node.bat.
● Se iniciará una shell de Erlang en la que podemos
introducir comandos.
Rareza nº 1

Hay que escribir un punto (.)


en la shell para finalizar una
expresión.
Operaciones aritméticas
1> 1 + 5. Cuidado con el punto!
6
2> 2 + (1 / 2). Cuidado con el punto!
1.5
3> 3 * 9 - 4. Cuidado con el punto!
23
Átomos
● Son constantes simbólicas, similares a los enumerados de C.
● Comienzan por letra minúscula y no tienen espacios.

1> hola.
hola
2> ok.
ok
3> qué_tal_estás.
qué_tal_estás
4> true.
true Los booleanos son átomos
5> 5 =< 4.
false
Variables
● Comienzan por letra mayúscula.

1> X = 4.
4
2> 2 * X + 6.
14
3> Edad = 35.
35
4> Jubilacion = 2018 + (67 - Edad).
2050
5> Z = 20, W = 15, Z * W. Secuencia de expresiones
300
6> X = 25.
** exception error: no match of right hand side value 25 ???
Rareza nº 2

Todas las variables son


inmutables.
Inmutabilidad
● Una vez que una variable toma un valor, no se le puede asignar otro distinto.
● Esto es muy característico de los lenguajes funcionales.

1> X = 4.
4
2> X = 5.
** exception error: no match of right hand side value 5
¿Y en la shell?
● ¿Tenemos que estar inventándonos variables continuamente para hacer
pruebas?
● No, porque tenemos el comando f() para “olvidar” variables.

1> X = 4, Y = 10, X + Y.
14
2> f(X). Borrar valor de X
ok
3> X + 1.
* 1: variable ‘X’ is unbound.
4> f(). Borrar valor de todas las variables definidas hasta el momento
ok
Nuestra primera función
Fichero hola_mundo.erl

saludar() ->
io:format("Hola, mundo!~n"). ~n = Fin de línea
Nuestra primera función
● Toda función debe estar definida dentro de un módulo.
● El módulo indica mediante -export qué funciones son visibles desde fuera
del módulo.
-module(hola_mundo).

saludar/0 = Función saludar


-export([saludar/0]). con cero parámetros

saludar() ->
io:format("Hola, mundo!~n").
Compilar el módulo y llamar a sus funciones
● Desde la shell:

1> c(hola_mundo).
{ok, hola_mundo}
2> hola_mundo:saludar().
Hola, mundo!
ok
Creamos una nueva función
● Esta vez tiene un parámetro.

-module(hola_mundo).

-export([saludar/0, saludar/1]).
...
saludar(Nombre) ->
io:format("Hola, ~s~n", [Nombre]).
Volvemos a la shell

1> c(hola_mundo).
{ok, hola_mundo}
2> hola_mundo:saludar(“Gloria”).
Hola, Gloria!
ok
Una modificación
● Saludar tantas veces como el número indicado por el primer parámetro.

1> hola_mundo:saludar(4, “Gloria”).


Hola, Gloria! (4)
Hola, Gloria! (3)
Hola, Gloria! (2)
Hola, Gloria! (1)
ok
Rareza nº 3

En Erlang no hay bucles


...pero sí hay recursión :-)
saludar(0, Nombre) -> ok; Caso base

saludar(Contador, Nombre) when Contador > 0 -> Caso recursivo

io:format("Hola, ~s (~w)~n", [Nombre, Contador]),


saludar(Contador - 1, Nombre).
...pero sí hay recursión :-)
saludar(0, _) -> ok; Caso base

saludar(Contador, Nombre) when Contador > 0 -> Caso recursivo

io:format("Hola, ~s (~w)~n", [Nombre, Contador]),


saludar(Contador - 1, Nombre).
¿Y si quiero un contador ascendente?

1> hola_mundo:saludar_asc(4, “Gloria”).


Hola, Gloria! (1)
Hola, Gloria! (2)
Hola, Gloria! (3)
Hola, Gloria! (4)
ok
¿Y si quiero un contador ascendente?
● Utilizamos un parámetro adicional.

saludar_asc(Contador, Max, _) when Contador > Max -> ok;


saludar_asc(Contador, Max, Nombre) ->
io:format("Hola, ~s (~w)~n", [Nombre, Contador]),
saludar_asc(Contador + 1, Max, Nombre).

saludar_asc(Max, Nombre) -> saludar_asc(1, Max, Nombre). Llamada


inicial
Tuplas
● Utilizadas para agrupar varios resultados.
● Sus componentes se delimitan entre { y }

decrement(0) -> {error, already_zero};


decrement(Num) -> {ok, Num - 1}.
Tuplas: ejemplo de uso

1> tuple_examples:decrement(5).
{ok, 4}
2> tuple_examples:decrement(0).
{error, already_zero}
Rareza nº 4

El ajuste de patrones
Acceder a los componentes de una tupla
● Mediante ajuste de patrones.

1> {X, Y} = tuple_examples:decrement(5).


{ok, 4}
2> X.
{ok, 4}
ok
3> Y.
4 {X, Y}
Acceder a las componentes de una tupla
● Si conozco que el resultado va a ser de la forma {ok, …}

1> {ok, Z} = tuple_examples:decrement(5).


{ok, 4} {ok, 4}
2> Z.
4
3> {ok, 2} = tuple_examples:decrement(3).
{ok, 2} {ok, Z}
4> {ok, V} = tuple_examples:decrement(0).
** exception error: no match of right hand side value
{error,already_zero}
Acceder a las componentes de una tupla
● Si conozco que el resultado va a ser de la forma {ok, …}

1> {ok, Z} = tuple_examples:decrement(5). {error, already_zero}


{ok, 4}
2> Z.
4
3> {ok, 2} = tuple_examples:decrement(3). {ok, V}
{ok, 2}
4> {ok, V} = tuple_examples:decrement(0).
** exception error: no match of right hand side value
{error,already_zero}
Expresiones case
● Distinción de casos + ajuste de patrones

decrement_print(N) ->
case decrement(N) of
{ok, NDec} ->
io:format("Resultado: ~w~n", [NDec]);
{error, _} ->
io:format("Error al decrementar.~n")
end.
Listas
1> Xs = [1, 3, ok, 10, 4, “Hola”].
[1,3,ok,10,4,"Hola"]
2> length(Xs).
6
3> length([]). [] = Lista vacía
0
4> Xs ++ [10, 30]. ++ = Concatenación de listas
[1,3,ok,10,4,"Hola",10,30]
5> lists:reverse(Xs).
["Hola",4,10,ok,3,1]
6> lists:member(3, Xs).
true
Listas intensionales
1> Xs = [1, 2, 3, 4, 5].
[1,2,3,4,5]
2> [ 2*X + 1 || X <- Xs ]. “Para todo X en Xs, devolver 2 * X + 1”
[3,5,7,9,11]
“Para todo X de Xs, tal que X es
3> [ 3*X || X <- Xs, X rem 2 == 0 ]. par, devolver 3 * X”
[6,12]
4> length([ X || X <- Xs, X rem 2 == 0]). Número de elementos pares
2
5> [ {X, Y} || X <- [1,2,3], Y <- [a,b] ].
[{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}]
Caracteres
1> $A.
65
2> $v.
118
3> $0.
48
4> $ .
128526
5> $ .
32
Rareza nº 5

Los caracteres se
representan mediante
números enteros.
Rareza nº 6

Las cadenas de texto son


listas de caracteres.
Cadenas: listas de caracteres
1> Cadena = “Qué hay de nuevo?”.
"Qué hay de nuevo?"
2> [$H, $o, $l, $a].
“Hola”
3> [72, 111, 108, 97].
“Hola”
4> lists:reverse(Cadena).
"?oveun ed yah éuQ"
5> length(Cadena)
17
6> lists:member($a, Cadena).
true
7> [ C || C <- Cadena, not lists:member(C, “aeiou”)].
"Qé hy d nv?"
Ejemplo
● Queremos hacer una función que reciba una cadena de
texto y sustituya todas las letras en el rango A..Z por
caracteres de subrayado (_).
Ejemplo
● Función para transformar un carácter individual:
hide_char(X) when X >= $A, X =< $Z -> $_;
hide_char(X) -> X.

● Función para transformar una cadena entera:


hide_string(String) -> [ hide_char(X) || X <- String ].
Ejecución de hide_string/1
1> string_utils:hide_string("HOLA, ME LLAMO MANU").
"____, __ _____ ____"
Una mejora
● Función hide_string(Cadena, Chars)

Realiza la sustitución de las letras en A..Z por caracteres


de subrayado, excepto aquellas que se encuentren la lista
Chars.
Una mejora
hide_char(X, Chars) when X >= $A, X =< $Z ->
case lists:member(X, Chars) of
true -> X;
false -> $_
end;
hide_char(X, _) -> X.

hide_string(String, Chars) ->


[ hide_char(X, Chars) || X <- String ].
Ejecución de hide_string/2
1> string_utils:hide_string("HOLA, ME LLAMO MANU", "ALM").
"__LA, M_ LLAM_ MA__"
2. Interacción entre procesos
El “Hola mundo” de los procesos (...literal)
● ¿Recuerdas la función saludar/2?
● Vamos a añadir un retardo de 5 segundos entre mensajes:

saludar(0, _) -> ok;


saludar(Contador, Nombre) when Contador > 0 ->
io:format("Hola, ~s (~w)~n", [Nombre, Contador]),
timer:sleep(5000), Retardo

saludar(Contador - 1, Nombre).
La función spawn
spawn(Modulo, Funcion, [Arg1, …, Argn])

● Ejecuta la Funcion (perteneciente a Modulo) pasándole


los parámetros Arg1, …, Argn.
● La ejecución de la función se realiza de modo concurrente
a la shell, en un proceso nuevo.
La función spawn
1> spawn(concurrente, saludar, [4, “Gloria”]).
Hola, Gloria (4)
<0.146.0> ¿Qué es eso?
Hola, Gloria (3)
Hola, Gloria (2)
Hola, Gloria (1)
PID (Process IDentifier)
● El valor <0.146.0> es el identificador del proceso recién
creado.
● La función spawn crea un proceso y devuelve su PID, el
cual podemos guardar en una variable.

1> Pid = spawn(concurrente, saludar, [4, “Gloria”]).



2> Pid.
<0.146.0>
¿Para qué sirve?
● El PID nos permite hacer referencia al proceso recién
creado para:
○ Poder enviar mensajes al proceso.
○ Forzar la terminación del proceso.
○ Asignar un nombre al proceso.
○ etc.
Recibir mensajes: la cláusula receive
● La cláusula receive detiene la ejecución de un proceso
hasta que llegue un mensaje a dicho proceso.
recibir_mensaje() ->
receive
Mensaje ->
io:format("He recibido un mensaje: ~w~n", [Mensaje])
end.
Enviar mensajes
● Se utiliza el operador !

Pid ! Mensaje

● Envía el Mensaje al proceso con el Pid dado.


1> Pid = spawn(concurrente, recibir_mensaje, []).

2> Pid ! “Hola”.
He recibido un mensaje: "Hola"
“Hola”.
2> Pid ! 34. ¡Ojo! El proceso está muerto
...
Finalización de un proceso
● Cuando finaliza la función recibir_mensaje, el proceso
creado por spawn muere.
recibir_mensaje() ->
receive
Mensaje ->
io:format("He recibido un mensaje: ~w~n", [Mensaje])
end.
Bucle de mensajes
● Si queremos que siga recibiendo mensajes, debemos
hacer una llamada recursiva:
recibir_varios_mensajes() ->
receive
Mensaje ->
io:format("He recibido un mensaje: ~w~n", [Mensaje]),
recibir_varios_mensajes() Llamada recursiva

end.
Bucle de mensajes
1> Pid = spawn(concurrente, recibir_varios_mensajes, []).

2> Pid ! “Hola”.
He recibido un mensaje: "Hola"
“Hola”
3> Pid ! {ok, [1,2,3]}.
He recibido un mensaje: {ok,[1,2,3]}
{ok,[1,2,3]}
Ajuste de patrones sobre el mensaje
● Podemos tener varias ramas en un receive, cada una de
ellas ajustándose a un patrón.
aritmetica() ->
receive
{suma, X, Y} ->
io:format("~w + ~w = ~w~n", [X, Y, X + Y]),
aritmetica();
{resta, X, Y} ->
io:format("~w - ~w = ~w~n", [X, Y, X - Y]),
aritmetica();
...
end.
Bucle de mensajes
1> PA = spawn(concurrente, aritmetica, []).

2> PA ! {suma, 10, 3}.
10 + 3 = 13
{suma,10,3}
3> PA ! {resta, 10, 3}.
10 - 3 = 7
{resta,10,3}
Guardar información entre mensajes (estado)
● Podemos utilizar los parámetros del bucle de mensajes.
contador(N) ->
receive
imprimir ->
io:format("Valor actual del contador: ~w~n", [N]),
contador(N);
incrementar ->
contador(N + 1)
end.
Guardar información entre mensajes (estado)
1> PC = spawn(concurrente, contador, [0]). Valor inicial = 0

2> PC ! imprimir.
Valor actual del contador: 0
3> PC ! incrementar.
4> PC ! incrementar.
5> PC ! incrementar.
6> PC ! imprimir.
Valor actual del contador: 3
Registro de procesos
● Es posible asignar un nombre a un proceso, y utilizar este
nombre para enviar mensajes, en lugar de PID.

1> register(mi_contador, PC). Asignamos el nombre mi_contador al


proceso cuyo PID es PC
true
2> mi_contador ! imprimir.
Valor actual del contador: 3
Comunicación entre procesos
● Estamos ejecutando varios procesos:
○ Proceso que imprime los mensajes P

que recibe. PA

○ Proceso que realiza operaciones PC

aritméticas.
○ Proceso contador.
Shell
● ¿Hay alguno más?
○ Sí. Muchos más que no vemos.
○ Entre ellos, la shell.
Comunicación entre procesos
● Cuando desde la shell tecleamos PA ! {suma, 3, 5},
el proceso shell envía un mensaje al proceso que realiza
operaciones aritméticas.
{suma, 3, 5}
Shell PA

● Hasta ahora, la comunicación es unidireccional.


¿Podemos hacerla bidireccional?

{suma, 3, 5}

Shell PA
{resultado, 8}
¿Podemos hacerla bidireccional?
● Sí, pero el servidor PA ha de enviar la respuesta mediante
el uso del operador !
● Para ello, necesita saber el PID del proceso que envió la
petición.
{suma, 3, 5}

Shell PA

??? {resultado, 8}
¿Podemos hacerla bidireccional?
● El remitente ha de incluir su PID en su petición.

{<0.30.0>, {suma, 3, 5}}

Shell PA

{resultado, 8}
<0.30.0>
Comunicación bidireccional
aritmetica_respuesta() ->
te
receive miten
Re
{From, {suma, X, Y}} ->
From ! {resultado, X + Y}, Enviar respuesta al remitente

aritmetica_respuesta();
...
end.
Comunicación bidireccional

1> PA = spawn(concurrente, aritmetica_respuesta, []).



2> PA ! { ??????, { suma, 3, 5 }}.
¿Q

PID
po
ne
mo
sa
qu
í?
Comunicación bidireccional

1> PA = spawn(concurrente, aritmetica_respuesta, []).



2> PA ! { self(), { suma, 3, 5 }}.

La función self() devuelve el


PID del proceso que la invoca.
Comunicación bidireccional
● La shell ha recibido una respuesta del proceso PA.
● ¿Cómo puedo ver los mensajes recibidos por la shell?

1> flush(). Imprimir mensajes recibidos por la shell


Shell got {resultado,8}
ok
Guess the movie

“BOHEMIAN RHAPSODY”
“”

{Pid, {guess, $A}}


GTM

________ ________
Guess the movie

“BOHEMIAN RHAPSODY”
“A”
{hit, 2, “______A_ __
A_____”}
GTM

______A_ __A_____
Guess the movie
1> GTM = gtm_server:start("Bohemian Rhapsody").
________ ________

2> GTM ! {self(), {guess, $O}}.
_O______ _____O__
3> flush().
Shell got {hit, 2, “_O______ _____O__”}
4> GTM ! {self(), {guess, $T}}.
_O______ _____O__
5> flush().
Shell got {miss, “_O______ _____O__”}
3. Interacción entre nodos
Nodos
● Un nodo es un sistema de Erlang en ejecución.
● Cada nodo puede estar identificado con un nombre.
● En un nodo puede haber varios procesos ejecutándose.

nodo1@PTO0520 nodo2@PTO0520
Envío de mensajes entre nodos
● Para enviar un mensaje a un proceso situado en otro
nodo, también se utiliza el operador !

{ NombreProceso, NombreNodo } ! Mensaje

El proceso ha de estar registrado


con un nombre
Envío de mensajes entre nodos

nodo1@PTO0520

1> P = gtm_server:start("Bohemian
Rhapsody").
________ ________

2> register(gtm, P).
true nodo2@PTO0520
______A_ __A_____
1> {gtm, nodo1@PTO0520} ! {self(), {guess, $A}}.
2> flush().
Shell got {hit,2,"______A_ __A_____"}
4. Interacción entre máquinas
Instrucciones
● Ejecuta el fichero play_gtm.bat
● Se abrirá un nodo con el nombre nodoclient@PTO05XX
● En la shell que aparezca, teclea:
gtm_server_enhaced:start(“Tu nombre”, “Nombre Pelicula”)

● Se creará un proceso registrado con el nombre gtm.


¡Que empiece el juego!
● Para obtener el título oculto de tu compañero, envíale un
mensaje:
{gtm, nodoclient@PTO05XX} ! {self(), get_title}

● Para intentar descubrir letras:


{gtm, nodoclient@PTO05XX} ! {self(), {guess, $X}}

● Tras hacer una petición, no olvides obtener la respuesta


recibida. Para ello utiliza flush().
Bibliografía
Joe Armstrong
Programming Erlang. Software for a Concurrent World (2nd edition)
The Pragmatic Bookshelf, 2013

Francesco Cesarini, Simon Thompson


Erlang Programming. A Concurrent Approach to Software Development
O’Reilly, 2009

Fred Hébert
Learn you some Erlang for Great Good!
[Link]

También podría gustarte