Breve introduccin
Erlang es un lenguaje de programacin multi-paradigma de propsito general. Se basa principalmente en los paradigmas funcional y concurrente (a travs del modelo de actor). Posee tipado dinmico y fuerte e incluye un recolector de basura. Fue creado para desarrollar aplicaciones de gran concurrencia, distribuidas y tolerante a fallas. Adems, soporta hot swapping, lo que significa que el cdigo puede ser cambiado durante la ejecucin del programa sin tener que detenerlo. La fortaleza principal de este lenguaje es su gran capacidad para la concurrencia, ya que los procesos Erlang son extremadamente livianos y giles, permitiendo la paralelizacin en miles, o incluso millones, de hilos que se comunican mediante intercambio de mensajes.
Historia del lenguaje
El lenguaje Erlang tiene sus orgenes en la compaa de telecomunicaciones Ericsson en el ao 1986. Esa primera versin fue desarrollada por Joe Armstrong y fue implementada en Prolog. En el ao 1998 fue liberado como open source, ya que las polticas de Ericsson en ese momento prohiban el uso de lenguajes privativos. La ltima versin estable (R14B02) data de marzo de 2011, con lo cual podemos ver que este lenguaje sigue en activo desarrollo.
Procedimientos, funciones y pasajes de parmetros
La definicin de una funcin se realiza de la siguiente forma: sumar(A, B) -> A+B. Si quisiramos realizar una sobrecarga podramos hacer lo siguiente: sumar(A, B) -> A+B; sumar(A, B, C) -> A+B+C. Podemos incluso hacer que la funcin se comporte de formas distintas dependiendo de los valores de los parmetros. Las distintas clusulas se definen usando pattern matching. Por ejemplo (en este caso circulo y cuadrado son tomos): area(circulo, Radio) -> Radio * Radio * 3.14159; area(cuadrado, Lado) -> Lado * Lado. Una funcionalidad interesante que provee Erlang son las guardas. Se trata de condiciones que uno puede establecer de forma tal de cambiar el comportamiento de una funcin de acuerdo al valor de los parmetros. De esta forma, podemos aumentar mucho el poder del pattern matching. Ejemplo: pagar(Cantidad) when Cantidad > 100 -> "Todo bien. Pagaste!";
pagar(Cantidad) -> "No alcanza". Tambin podemos usar guardas para hallar otra forma de pensar la funcin mximo, sin usar un if: maximo(X, Y) when X > Y -> X; maximo(X, Y) -> Y.
Sistema de tipos
Erlang posee tipado dinmico, lo cual significa que cualquier error de tipos que haya ser atrapado en tiempo de ejecucin y el compilador no presentar quejas al respecto. Si bien se podra pensar que el tipado esttico es ms seguro (ya que el compilador tiene la posibilidad de capturar los problemas incluso antes de que el cdigo se ejecute), Erlang ha probado, tras ser muy utilizado en distintos equipos de comunicaciones, que es extremadamente estable y confiable. La filosofa al respecto es que los errores de tipado pueden suceder, pero deben quedar cubiertos y no interferir con el resto de la ejecucin del programa. Adems, Erlang es fuertemente tipado y no realiza conversiones implcitas entre tipos a la hora de efectuar operaciones. As es que no se pueden realizar cosas tales como 3 + 4, que son perfectamente vlidas en lenguajes dbilmente tipados, tales como PHP. La librera de Erlang posee funciones para la conversin entre tipos, para usar en aquellos momentos en los que necesitemos, por ejemplo, convertir un entero a punto flotante. En general, esas funciones llevan un nombre tipo1_to_tipo2, como por ejemplo: integer_to_list, atom_to_list, list_to_tuple, etc. Para chequear si el valor de una variable es de un tipo determinado, la librera tambin provee un conjunto de funciones con nombre is_tipo, como por ejemplo: is_binary, is_boolean, is_number, etc.
Tipo provistos por el lenguaje
Erlang provee un conjunto de diez tipos de dato, ocho simples y dos compuestos. A saber: Enteros: son de precisin arbitraria. Flotantes: usan representacin de 64 bits, tienen un rango de aproximadamente 10308. tomos: son palabras que comienzan con letra minscula (y continan con cualquier secuencia alfanumrica) y son usados por el desarrollador a modo de constantes literales. Se almacenan en una tabla, as que nunca deberan ser creados dinmicamente. No son recolectados por el garbage collector. Tuplas: son contenedores de un nmero fijo de datos. La sintaxis es {Dato1, Dato2 DatoN}. Listas: son contenedores de un nmero variable de datos. Sus componentes pueden ser de cualquiera de los tipos expresados. La sintaxis, azcar sintctica mediante, es [Dato1, Dato2 DatoN]. Referencias: son smbolos que se utilizan para comparar si dos variables estn apuntadas a un mismo espacio de memoria. Se crean con la instruccin make_ref.
Puertos: son mecanismos de comunicacin con el mundo exterior al programa. Se usan para enviar y recibir secuencias de bytes. Por ejemplo, leer datos de stdin. Binarios: son secuencias de bytes binary safe para almacenar y realizar E/S de binarios de forma eficiente. Funs: son los closures de las funciones. Pids: son referencias a procesos de la mquina virtual de Erlang.
Estructuras de datos
Adems de las listas y tuplas, Erlang provee otras estructuras de datos por medio de sus libreras. Arrays Los arrays en Erlang no son soportados en forma nativa, sino que vienen en una librera aparte llamada array. Esta implementacin de los arrays tiene las siguientes caractersticas: Pueden ser tanto de tamao fijo como automticamente extensibles. Utilizan indexacin en base cero. Los datos no definidos son por defecto inicializados como undefined. Los arrays fijos se crean utilizando: A0 = array:new(10). Los arrays extensibles se crean con: A1 = array:new(). Los arrays fijos pueden hacerse extensibles utilizando relax/1 y los extensibles hacerse fijos usando fix/1. Contienen la funcionalidad bsica que se puede esperar de un array (agregar elementos, modificarlos, mapear, etc). Pueden ser transformados a listas o diccionarios ordenados con sparse_to_list(Array::array()) -> list() y sparse_to_orddict(Array::array()) -> [{Index::integer(), Value::term()}] respectivamente. Debido a que la memoria de Erlang es inmutable, esta implementacin de los arrays es poco eficiente en comparacin a las listas y otras estructuras nativas, y debe evitarse utilizarlo con grandes conjuntos de datos.
Records
Los registros en son simplemente azcar sintctico agregado a tuplas. Todo lo que se realiza con registros podra realizarse sin ellos de una forma similar. Para declarar un registro se utiliza: -module(records). -compile(export_all).
-record(usuario, {id, nombre, grupo = comun, edad}). Esto define un registro del tipo usuario con los campos id, nombre, grupo y edad. El campo grupo es inicializado por defecto en comn. Los dems campos se inicializan por defecto con undefined. La funcionalidad principal de los registros es utilizarlos en guardas o en la definicin de una funcin: admin_panel(#usuario{nombre=Nombre, grupo=admin}) -> Nombre ++ "tiene acceso permitido"; admin_panel(#usuario{nombre=Nombre}) -> Nombre ++ "no tiene acceso permitido". Se puede acceder a los campos utilizando ., por ejemplo: User = #usuario{id=100,nombre=Pablo, grupo=admin, edad=30}. 100 == User#[Link] Ok Para poder crear registros sin tener que recompilar el cdigo se puede crear archivos header, por ejemplo: -record(registro1,{campo1,campo2,campo3}). Guardado como [Link] Y luego se agrega a un modulo, incluyendo en ese modulo la sentencia: -include([Link]).
Almacenamiento por claves
Aqu Erlang ofrece varias alternativas. Algunas de ellas son: Proplists: Una estructura cuya nica condicin es que sea una lista de tuplas de la forma {clave,valor}. Erlang provee algunas funciones generales para este tipo de estructuras, como proplists:delete/2, proplists:get_value/2, proplists:get_all_values/2, proplists:lookup/2 y proplists:lookup_all/2. Ejemplo: > Actividades = [{lunes, [trabajo, facultad]},{martes, [trabajo, facultad]},{miercoles, [trabajo, facultad]},{jueves, [trabajo, facultad]},{viernes, [trabajo]}, {sabado, []}, {domingo, []}, {lunes, [dormir]}]. [{lunes,[trabajo,facultad]}, {martes,[trabajo,facultad]}, {miercoles,[trabajo,facultad]}, {jueves,[trabajo,facultad]}, {viernes,[trabajo]},
{sabado,[]}, {domingo,[]}, {lunes,[dormir]}] > proplists:is_defined(lunes, Actividades). true > proplists:is_defined(claveInexistente, Actividades). false > proplists:get_value(lunes, Actividades). [trabajo,facultad] > proplists:get_all_values(lunes, Actividades). [[trabajo,facultad],[fruta]] > proplists:delete(martes, Actividades). [{lunes,[trabajo,facultad]}, {miercoles,[trabajo,facultad]}, {jueves,[trabajo,facultad]}, {viernes,[trabajo]}, {sabado,[]}, {domingo,[]}, {lunes,[dormir]}] > proplists:lookup(lunes, Actividades). {lunes,[trabajo,facultad]} > proplists:lookup_all(lunes, Actividades). [{lunes,[trabajo,facultad]},{lunes,[dormir]}] Orddicts: Estructuras que adems de cumplir la condicin de las proplist, deben cumplir que cada clave solo puede estar una vez, y que las claves deben estar ordenadas. Las funciones clave de los orddicts son: orddict:store/3, ordict:find/2, orddict:fetch/2, orddict:erase/2. Ejemplos: > Diccionario = orddict:new(). [] > Diccionario1 = orddict:append("clave1", "valor1", Diccionario). [{"clave1",["valor1"]}] En esta funcion, si no existe la clave, se la agrega.
> Diccionario2 = orddict:append_list("clave1", ["valor1-a","valor1b"], Diccionario1). [{"clave1",["valor1","valor1-a","valor1-b"]}] La funcin falla si el valor asociado no es una lista. > Diccionario3 = orddict:fetch("clave1", Diccionario2). ["valor1","valor1-a","valor1-b"] Si no existe la clave, la funcin falla. > orddict:fetch_keys(Diccionario3). ["clave1"] > orddict:find("clave1", Diccionario3). {ok,["valor1","valor1-a","valor1-b"]} > orddict:find("claveInexistente", Diccionario3). error Devuelve una tupla {ok, valor} en caso de existir la clave o error en caso contrario. > orddict:is_key("clave1", Diccionario3). true > orddict:is_key("claveInexistente", Diccionario3). false > Diccionario3 = orddict:erase("clave1", Diccionario3). [] % Dic1 = [{"a",[1]}] > orddict:map(fun(Key, Value) -> lists:map(fun(X) -> X * 2 end, Value) end, Dic1). [{"a",[2]}] % Dic1 = [{a,[1]},{b,[2]}] % Dic2 = [{b,[3]},{c,[3]}] % Fun = fun(Clave, Valor1, Valor2) -> lists:sum(Valor1) + lists:sum(Valor2) end. > orddict:merge(Fun, Dic1, Dic2). [{a,[1]},{b,5},{c,[3]}] % Dic = [{a,[1]},{b,[2]}] > orddict:size(Dic). 2 % Dic = [{a,[1]},{b,[2]}] > orddict:store(c, [3], Dic) [{a,[1]},{b,[2]},{c,[3]}]
% Dic = [{a, [1,2,3]}] % Fun = fun(X) -> lists:reverse(X) end. > orddict:update(a, Fun, Dic). [{a,[3,2,1]}] % Dic = [{a, [1,2,3]}] % Fun = fun(X) -> lists:reverse(X) end. orddict:update(b, Fun, 1, Dic). [{a,[1,2,3]},{b,1}] orddict:update(a, Fun, 1, Dic). [{a,[3,2,1]},{b,1}] Dicts: Tienen las mismas caracteristicas que los orddicts, pero estan optimizados para una mayor cantidad de elementos. Tambien provee exactamente las mismas funciones que orddict. Tablas ETS, DETS: Tablas para guardar una gran cantidad de datos. Ademas estn implementadas de forma concurrente (cada tabla es un proceso aparte, y por lo tanto tiene una memoria separada). Nunca son recolectadas por el gc, sino que deben ser eliminadas manualmente con delete/1.
Comprensin de listas
Erlang implementa una forma de construir listas utilizando para ello condiciones. El crear listas de esta forma permite que el cdigo sea corto y sencillo, construyendo listas de una forma muy similar a la definicin de conjuntos matemticos. A continuacin un ejemplo para construir una lista de enteros pares del 0 al 10. [2*X || X <- [0,1,2,3,4,5]]. Esto devuelve una lista [0, 2, 4, 6, 8, 10] ya que aplica la funcin 2*X a cada elemento de la lista original [0, 1, 2, 3, 4, 5]. Otro ejemplo, que sirve para obtener los mltiplos de 3 que hay dentro de una lista es: [X || X <- [0,1,2,3,4,5,6,7], X rem 3 == 0]. Efectivamente, el resultado obtenido ser [0,3,6]. La funcin rem devuelve el resto de una divisin entera y la flecha <- indica los valores que puede tomar X. La sintaxis general es: NuevaLista = [Expresion || Patron <- Lista, Cond1, Cond2, ... CondN]
Tratamiento de listas
lists:all(Condicion, Lista). Ejemplo: > lists:all(fun(X) -> X > 0 end, [1,2,3,4]). true > lists:all(fun(X) -> X > 0 end, [0,1,2,3]).
false lists:any(Condicion, Lista). Ejemplo: > lists:any(fun(X) -> X rem 2 == 1 end, [2,4,6,8]). false > lists:any(fun(X) -> X rem 2 == 1 end, [2,4,6,7]). true lists:append([Lista1, Lista2, .... , ListaN]). Ejemplo: > lists:append([[1,2,3,4],[5,6,7,8]]). [1,2,3,4,5,6,7,8] lists:append(Lista1, Lista2). Ejemplo: > lists:append([1,2,3],[4,5,6]). [1,2,3,4,5,6] lists:delete(Elem, Lista). Ejemplo: > lists:delete(1, [1,2,3,4,5]). [2,3,4,5] lists:dropwhile(Condicion, Lista). Ejemplo: > lists:dropwhile(fun(X) -> X < 5 end, [1,2,3,10,11,12,13,15]). [10,11,12,13,15] lists:filter(Condicion, Lista). Ejemplo: > lists:filter(fun(X) -> X rem 2 == 0 end, [0,1,2,3,4,5,6,7,8,9]). [0,2,4,6,8] lists:last(Lista). Ejemplo: > lists:last([0,1,2,3,4]). 4 lists:nth(N, Lista). Ejemplo: > lists:nth(4, [1,2,3,4,5,6]). 4 lists:map(Funcion, Lista).
Ejemplo: > lists:map(fun(X) -> math:pow(X, 2) end, [1,2,3,4,5]). [1.0,4.0,9.0,16.0,25.0] lists:max(Lista). Ejemplo: > lists:max([1,2,3,4,5]). 5 lists:min(Lista). Ejemplo: > lists:min([1,2,3,4,5]). 1 lists:member(Elemento, Lista). Ejemplo: > lists:member(2, [1,2,3,4,5]). true > lists:member(9, [1,2,3,4,5]). false lists:partition(Condicion, Lista). Ejemplo: > lists:partition(fun(X) -> X > 0 end, [-3,-2,-1,0,1,2,3]). {[1,2,3],[-3,-2,-1,0]} lists:reverse(Lista). Ejemplo: > lists:reverse([1,2,3,4]). [4,3,2,1] lists:seq(Desde, Hasta). Ejemplo: > lists:seq(1, 10). [1,2,3,4,5,6,7,8,9,10] lists:seq(Desde, Hasta, Incremento). Ejemplo: > lists:seq(1, 10, 2). [1,3,5,7,9] lists:sort(Lista). Ejemplo: > lists:sort([3,1,7,0,6]). [0,1,3,6,7]
Otra posibilidad del sort: lists:sort(Funcion(X, Y), Lista). lists:sublist(Lista, Cantidad). Ejemplo: > lists:sublist([1,2,3,4,5,6,7], 3). [1,2,3]
lists:sublist(Lista, PosicionDeComienzo, Cantidad). Ejemplo: > lists:sublits([1,2,3,4,5,6,7], 3, 4). [3,4,5,6] lists:subtract(Lista1, Lista2). Ejemplo: > lists:subtract([1,2,3,4,5], [2,3,4,5]). [1]
Tipos definidos por el usuario
En Erlang el soporte para tipos definidos por el usuario no es muy rico. Una herramienta til que posee el lenguaje, implementado en realidad mediante trucos del precompilador, son los registros, estructuras de datos semejantes a los structs de C. Un registro se define, por ejemplo, de la siguiente forma: -record(fecha, {dia, mes, anio}). En ese caso estamos definiendo un tipo de registro con el nombre fecha que adentro posee tres campos llamados dia, mes y anio. Un ejemplo de uso de ese registro, para mostrar cmo se instancia y cmo se accede a sus campos es el siguiente: mostrarFecha(UnaFecha) -> Dia = UnaFecha#[Link], Mes = UnaFecha#[Link], Anio = UnaFecha#[Link], io:format("~p del mes ~p de ~p~n", [Dia,Mes,Anio]). prueba() -> Fecha = #fecha{dia=5, mes=11, anio=2009}, mostrarFecha(Fecha).
Estructuras de control
Las estructuras de control estndar, tales como los if, no son muy usadas en el lenguaje Erlang, gracias al gran poder del pattern matching y la recursin. Es interesante notar que este lenguaje no incluye estructuras para hacer ciclos (como for y while). Es el propio programador quien debe crearlas, mediante el uso de recursin. Si bien puede parecer un tanto tedioso, termina resultando en un mayor grado de control y libertad para el desarrollador. En Erlang tenemos tres estructuras de control, a saber:
if
El if en realidad comparte la misma estructura que las guardas, vistas anteriormente. El efecto del else se consigue mediante la evaluacin de la expresin true, que correspondera al else o al caso por defecto de un switch en otros lenguajes. Es importante notar que si ninguna de las guardas del if se cumple, se producir un error. Nunca deben quedar casos sin contemplar! if Guarda1 -> Secuencia1; Guarda2 -> Secuencia2; ... GuardaN -> SecuenciaN; true -> SecuenciaPorDefecto end
case
La estructura case funciona de forma muy similar a la del lenguaje Oz, pero con el agregado de guardas, al igual que en las definiciones de funciones. Sintaxis: case UnValor of Patron1 -> Secuencia1; Patron2 -> Secuencia2; ... end Un ejemplo: haceCalor(Temperatura) -> case Temperatura of {kelvin, Grados} when Grados > 298 -> true; {celsius, Grados} when Grados > 25 -> true; _ -> false end.
receive
La estructura receive se utiliza en concurrencia, para poder, como su nombre indica, recibir mensajes desde otros procesos. Se hace uso de pattern matching para determinar el flujo del comportamiento. Sintaxis: receive Patron1 -> Secuencia1;
Patron2 -> Secuencia2; ... end
bucles
En Erlang no existen estructuras como el for y el while pero pueden ser fcilmente emuladas mediante recursin. Un ejemplo de una implementacin de for, a la que le pasamos una funcin para que sea invocada en cada iteracin: bucleFor(Comienzo, Final, Funcion) when Comienzo < Final -> Funcion(Comienzo), bucleFor(Comienzo + 1, Final, Funcion); bucleFor(Comienzo, Final, Funcion) -> Funcion(Comienzo).
Manejo de errores
Errores en tiempo de compilacin
Estos errores son los que el compilador encuentra al intentar compilar el programa. Generalmente son errores de sintaxis, de identificadores incorrectos o de tipado. Ejemplos:
Errores en tiempo de ejecucin
Errores que aparecen durante la ejecucin de un programa. Se distinguen 3 tipos de ellos: Errores Estos errores terminan la ejecucin del proceso en donde fueron originados. Aparecen cuando sucede algo no previsto por el cdigo del programa. Son los tipos error que Erlang enva al Shell cuando no puede continuar con la ejecucin. Tambien se pueden definir errores personalizados. Este tipo de error incluye un seguimiento de pila con las ultimas llamadas a funciones que se realizaron antes del error y los argumentos que estas recibieron. Exits Hay 2 tipos de salidas: Exit/1: Son similares a los errores, con la nica diferencia de que no devuelven un seguimiento de pila. Se utilizan cuando la intencin es simplemente terminar el procedimiento, bien porque el error no da informacin til o porque se tienen muchas llamadas recursivas de funciones y es poco practico copiar el seguimiento. Si se llama la funcin exit con el tomo normal como parmetro, se indica que el proceso se ha terminado normalmente y no con un error. Exit/2: Se usa para enviar un mensaje de terminacin a un proceso determinado. El primer parmetro es el PID y el segundo, la razn por la cual es finalizado.
Throws Estos son los errores que el programador puede manejar sin terminar el procedimiento. Estos errores en general envan un tomo (excepcin) o un valor que identifican casos particulares de comportamiento de los procesos, que no necesariamente los terminan.
Excepciones
Para manejar los errores se utilizan las excepciones, en forma similar a otros lenguajes. Se utilizan las clausulas tryofcatchafter de la siguiente forma: try Expression of Patron1 [Guarda] -> Expresion1; Patron2 [Guarda] -> Expresion2; ... catch TipoDeError: PatronDeExcepcion1 -> Expresion3; TipoDeError: PatronDeExcepcion2 -> Expresion4; ... after Expresion5 ... ExpresionN end. El segmento de cdigo entre try y of es el segmento protegido, y cualquier excepcin que ocurra all ser capturada y se comparara con los patrones de catch. El segmento After se ejecuta siempre, ya sea que ocurre excepcin o no. Ejemplo: try operarElementos(arg1,arg2) of _ -> ok catch throw:argumento_invalido -> los argumentos deben ser positivos; error:Error -> {error,caught,Error}; exit:error_fatal -> exploto todo end. Puede haber varias operaciones entre try y of o entre try y catch. Puede omitirse la sentencia of, pero no debe llamarse a una funcin recursiva muy extensa en la seccin protegida, ya que puede incurrir en un agotamiento de memoria.
Clusula catch Otra herramienta que ofrece Erlang es la de utilizar simplemente la clusula catch la cual es equivalente a utilizar un try catch, pero devuelve los errores en un formato predeterminado. Si la expresin dentro del catch no devuelve error, sino un valor, entonces el catch devuelve ese valor. Ejemplo: X = (catch 4/2) X = 2 X = (catch 4/0) {'EXIT',{badarith,[{erlang,'/',[4,0]}, {erl_eval,do_apply,1}, {erl_eval,expr,1}, {shell,exprs,2}, {shell,eval_exprs,2}, {shell,eval_loop,2}]}}
Errores en Procesos
Erlang ofrece varias herramientas para tratar errores que puedan terminar procesos. Links Los links son enlaces entre procesos, cuya function es eliminar todos los procesos enlazados cuando alguno de ellos muere. Esto se utiliza para evitar que un grupo de procesos opere con referencias vacias, pudiendo extender el error al resto del programa. En lugar de eso, se mata a todos los procesos afectados y se reinicia ese conjunto. Para enlazar 2 procesos se utiliza link/1 y para desenlazarlos se utiliza unlink/1. Tambien se puede utilizar spawn_link/1 en caso de querer enlazar un proceso en el momento de su creacin. Cuando un proceso enlazado muere enva un mensaje a sus enlaces con el motivo del error (si es que lo reconoce).
proceso() -> receive _ -> P_Linkeado = spawn_link(fun() -> receive _ -> exit(causa) end end), P_Linkeado ! termina, proceso() end. {Proceso,_} = spawn_monitor(practica,proceso,[]). Proceso hola. flush(). Shell got {'DOWN',#Ref<[Link]>,process,<0.82.0>,causa}
Shell vigilaba a Proceso, y el mensaje solo eliminaba a P_Linkeado, sin embargo Proceso tambien cayo al morir P_Linkeado.
Monitors Los monitors tambin son enlaces entre procesos, pero tienen 2 diferencias con los links: Son unidireccionales Pueden declararse varios monitors en un mismo proceso hacia otros. Su funcionalidad principal es avisar a los procesos fuente si los procesos apuntados estn vivos o no. Cuando un proceso apuntado por un monitor muere, enva un mensaje con el siguiente formato: {'DOWN', ReferenciaMonitor, process, Pid, motivo} Los monitores simplemente llamando a la function con el atomo process y un identificador de proceso Pid erlang:monitor(process, Pid). Al igual que los link, hay una funcin para crear un proceso y monitorearlo de forma atmica en una sola sentencia. Ejemplo: {Proceso,Referencia} = spawn_monitor(fun() -> receive _ -> exit(terminado) end end). Proceso ! mensaje. flush(). Ejecutar ese cdigo en el Shell nos devuelve: Shell got {'DOWN',#Ref<[Link]>,process,<0.37.0>,terminado} Proceso envi un mensaje al proceso que lo vigilaba (Shell), dicindole que Proceso cay y que la causa es terminado. Al igual que los links, se puede dejar de vigilar un proceso utilizando erlang:demonitor(process,Pid). Proceso de sistema Cualquier proceso puede ser declarado proceso de sistema agregando la lnea process_flag(trap_exit, true) a su cdigo. Los procesos de sistema no mueren aunque reciban mensajes de terminacin de otros procesos linkeados. En lugar de ello, transforman los mensajes de salida en mensajes comunes. Reinicio de procesos Los links y los procesos de sistema pueden ser utilizados para reiniciar procesos cados. Para esto se utilizan adems otras 2 funciones: register(nombre, Pid). whereis(nombre). Utilizando register se puede ligar un proceso a un tomo, y luego utilizando whereis se puede encontrar el Pid del proceso reiniciado. Ejemplo (aca voy a poner un ejemplo que sintetiza todo lo de manejo de errores):
Soporte de concurrencia
Como dijimos anteriormente, el punto ms fuerte del lenguaje es su excelente soporte para la concurrencia. Incorpora un conjunto de primitivas muy sencillas para el manejo y la comunicacin entre procesos. Los procesos de la mquina virtual en Erlang son extremadamente livianos (generan una carga en memoria de alrededor de 2 kilobytes) y se comunican entre s mediante pasaje de mensajes en forma asncrona. Los distintos hilos de ejecucin no poseen ningn estado compartido entre s y slo se comunican mediante mensajes, reduciendo la probabilidad de errores. Muchas veces se suele describir esta arquitectura de concurrencia con una metfora: un conjunto de habitaciones con una persona encerrada en cada una de ellas con una mquina de escribir. Esas personas reciben mensajes en su buzn, toman slo aquellos que entienden, y los responden nuevamente a travs del buzn. Esto permite que uno le puede solicitar a cada persona que realice tareas muy especficas sin que interfiera con las dems y ser avisado cuando la tarea fue concluida.
Procesos
En Erlang los procesos son funciones que tienen varias caractersticas: Cada uno se ejecuta en un hilo separado. Para crearlos se utiliza la funcin spawn, que recibe la funcin con la que se creara el proceso, y tambin puede recibir el mdulo y los argumentos de la funcin. Para una funcin sin parmetros: Proceso1 = spawn(funcion1). En el caso de una funcin con parmetros y que se quiere especificar su mdulo origen: Proceso1 = spawn(modulo1, funcion1, [param1, param2 ... ]) Aqu Proceso1 es llamado un PID o identificador de proceso y es simplemente un nombre para identificar al proceso y luego poder enviarle mensajes. Cada proceso tiene una memoria separada de los dems, una caracterstica importante para que el sistema se mantenga a largo plazo. Al estar separadas las memorias, el paso de parmetros siempre se realiza por valor. Todos los procesos deben implementar la clausula receive, la cual toma un mensaje del buzn y lo compara con los mensajes especificados en receive utilizando pattern matching. Esto define los mensajes que el proceso puede entender. Se puede especificar adems un comportamiento para todos los mensajes que el proceso no comprende utilizando la variable especial _. proceso1() -> receive Mensaje1 -> Cuerpo1; ... MensajeN -> CuerpoN; _ -> RespuestaPorDefecto end.
La clusula receive puede adems implementar guardas: receive Patron1 when Guarda1 -> Expresion1; Patron2 when Guarda2 -> Expresion2; Patron3 -> Expresion3 end. Para enviar mensajes a los procesos se utiliza el operador ! de la siguiente forma: Proceso1 ! Mensaje1. Los mensajes no comprendidos por el proceso destino simplemente son ignorados (se los quita del buzn). Pueden enviar referencias a si mismos utilizando la expresin self(), y pueden referirse a quien envi el mensaje si es pasado como parmetro en el mensaje: proceso1() -> receive {Remitente, Mensaje} -> Remitente ! UnaRespuesta, proceso1() end. ElProceso = spawn(proceso1). ElProceso ! {self(), UnMensaje}. El cdigo anterior define un proceso proceso1() y le enva un mensaje UnMensaje, a lo cual este responde con UnaRespuesta. Adems, se agreg una lnea proceso1() en la respuesta al mensaje. Esto se debe a que cuando un proceso ejecuta un receive, lo hace 1 sola vez, y una vez que la ejecucin termin el proceso queda finalizado. Para tener un proceso que esta continuamente recibiendo mensajes es necesaria una llamada recursiva. el Erlang Shell es tambin un proceso, entonces cuando se enva un mensaje a un proceso directamente desde el Shell y este proceso responde al remitente del mensaje, la respuesta ser enviada al buzn de mensajes del Shell. Este se puede ver utilizando flush(). Los procesos adems cuentan con una clausula after x. Esta sirve para ejecutar un trozo de cdigo cuando el proceso no ha recibido una respuesta luego de x milisegundos, es decir sirve para especificar un timeout.
Manejo de memoria y recoleccin de basura
En Erlang la memoria no se borra o se asigna como en C, sino que cuenta con un recolector de basura. El recolector de basura de Erlang comienza siendo simplemente un compactadorcolector, y se transforma en un colector generacional cuando los procesos se hacen muy grandes. Ademas, el recolector de basura funciona por proceso, es decir que en cada proceso la recoleccin se administra por separado, e incluso es posible tener procesos con un heap fijo,
que se borran al terminar de usarse. Esta recoleccin por procesos puede resultar muy eficiente en algunos casos, ya que el tiempo de recoleccin para procesos pequeos es casi imperceptible. Sin embargo tambin hay casos en que este modelo resulta poco eficiente, como por ejemplo el peor caso posible, en el que todos los procesos llegan a su mximo de memoria al mismo tiempo. Este recolector es mas sencillo que el de otros lenguajes, ya que Erlang utiliza memoria inmutable, entonces es imposible que haya una generacin anterior de datos apuntando a una mas nueva. Hay datos que son guardados en lugares con diferente comportamiento para el recolector de basura: Binarios > 64 bytes Los binarios se colocan en un heap separado, fuera del scope del proceso. Ademas, como no pueden, por definicin, contener punteros a otros segmentos de memoria, por lo que son reference counted (se eliminan cuando hay mas referencias a ellos). Tablas ETS Para cada key de la tabla, su memoria esta guardada en el proceso que la origino. Esto puede ser lento en caso de que se deba copiar valores muy grandes, sin embargo permite asegurar que el recolector de basura no tenga que copiar todos los datos de la tabla en un mismo momento. Nombres de tomos Los strings de los tomos son guardados en segmentos separados de memoria, y nunca son recolectados. Si bien esto es mas eficiente, es el motivo por el que no deben crearse tomos en forma dinmica en Erlang. (en otros lenguajes como Lisp los tomos si son recolectados)
Usos del lenguaje
Desde su liberacin en 1998, Erlang empez a ser utilizado por varias compaas en todo el mundo, incluyendo Nortel y T-Mobile. Gracias a la facilidad y robustez que otorga en tareas de alta concurrencia, es muy utilizado en implementaciones de servidores, sistemas distribuidos, bases de datos y sistemas de telecomunicaciones. Algunos proyectos en que es utilizado el lenguaje: Bluetail/Alteon/Nortel: sistema de correos distribuido y tolerante a fallos. Cellpoint: servicios de localizacin a mviles. Corelatus: monitorizacin de redes SS7 (protocolo de seales de telefona). Instituto Meteorolgico de Finlandia: recogida y monitorizacin de datos en tiempo real. IDT: clculo y costes en tiempo real de enrutamiento de sistemas expertos. Kreditor: sistemas de pago electrnico. RabbitMQ: software de mensajera. Mobilearts: sistema para servicios de GSM y UMTS. Netkit Solutions: monitorizacin de equipos de red y soporte a sistemas operativos.
Schlund Partner: mensajera y servicios de respuesta interactiva a voz. Quviq: software para testeo de aplicaciones. T-Mobile: servicio avanzado de control de llamadas. Telia: software para un operador de telecomunicaciones. Vail Systems: sistema interactivo de respuesta a voz. Wavenet: aplicacin SS7 y IVR. CouchDB: base de datos no relacional. Zotonic: CMS (Content management system). Ejabberd: sistema de mensajera basado en Jabber. Yaws: implementacin de un servidor web. Hypernumbers: hoja de clculo online, similar a Google Docs. OpenPoker: servidor open source de poker. Heroku's Routing Mesh: infraestructura y sistemas para cloud computing. Tsung: herramienta multiprotocolo para realizar test de carga. Winds3D: programa de modelado 3D.
Debilidades del lenguaje
No es un lenguaje extremadamente popular, por lo cual puede ser complicado hallar otros desarrolladores o soluciones a problemas complejos en la comunidad. No posee buenas bibliotecas para manejo de cadenas de texto. No es til para desarrollar aplicaciones de negocio, ya que suelen tener un ciclo de vida corto, con muchos cambios de especificaciones y abundantes reglas de negocio que no siempre son fciles de modelar en un lenguaje como Erlang. Es difcil desarrollar aplicaciones que requieran transacciones atmicas (por ejemplo una transferencia bancaria), ya que en Erlang todo se hace en forma concurrente mediante paso de mensajes.
Backtracking
-module(bt). -export([backtracking/0, persona/2, iterarPosibilidades/3, mostrador/0]). persona(DiasQuePuedeTrabajar, Siguiente) -> receive {completar, ListaActual} -> DiasQuePuedoAportar = lists:subtract(DiasQuePuedeTrabajar, ListaActual), iterarPosibilidades(DiasQuePuedoAportar, ListaActual, Siguiente), persona(DiasQuePuedeTrabajar, Siguiente) end.
iterarPosibilidades(DiasQuePuedoAportar, ListaActual, Siguiente) ->
case DiasQuePuedoAportar of [] -> skip; [H|T] -> Siguiente ! {completar, ListaActual ++ [H]}, iterarPosibilidades(T, ListaActual, Siguiente) end.
mostrador() -> receive {completar, Fruta} -> io:format("Solucion: ~p~n", [Fruta]), mostrador() end.
backtracking() -> Mostrador = spawn(bt2, mostrador, []), Per5 = spawn(bt2, persona, [[3,4,5], Mostrador]), Per4 = spawn(bt2, persona, [[3,4], Per5]), Per3 = spawn(bt2, persona, [[1,2,3,5], Per4]), Per2 = spawn(bt2, persona, [[2,5,3], Per3]), Per1 = spawn(bt2, persona, [[2,3,4,5], Per2]), Per1 ! {completar, []}, ok.