Diferencia entre revisiones de «Programación en Erlang»

Contenido eliminado Contenido añadido
m +Plantilla:Subpaginas.
Sin resumen de edición
Línea 811:
Las expresiones del Guard permitido (a veces se llaman pruebas de guard) es un subconjunto del conjunto de las expresiones válidas Erlang, desde la evaluación de una expresión de guard debe ser garantizado de estar libre de efectos secundarios.
 
{|Border=1
|+Expresiones Validas de los Guards
|-
|atom true
|-
|Otras constantes (términos y variables ligadas), todos considerados como falsos
|-
|Comparacion de terminos
|-
|Expresiones aritmeticas y booleanas
|-
|Llamadas al BIFs (Built-in functions) especificadas abajo
|-
!Tipo de prueba BIFs
!Otros BIFS permitidos en los guards
|-
|is_atom/1
|abs(Integer | Float)
|-
|is_constant/1
|float(Term)
|-
|is_integer/1
|trunc(Integer | Float)
|-
|is_float/1
|round(Integer | Float)
|-
|is_number/1
|size(Tuple|Binary)
|-
|is_reference/1
|element(N, Tuple)
|-
|is_port/1
|hd(List)
|-
|is_pid/1
|tl(List)
|-
|is_function/1
|length(List)
|-
|is_tuple/1
|self()
|-
|is_record/2 The 2nd argument is the record name
|node()
|-
|is_list/1
|node(Pid|Ref|Port)
|-
|is_binary/1
|
|}
 
pequeño ejemplo:
fact(N) when N>0 -> % first clause head
N * fact(N-1); % first clause body
fact(0) -> % second clause head
1. % second clause body
 
===Tail Recursion===
Si la última expresión de una función es una llamada de función, una llamada recursiva de tail se realiza de tal manera que no hay recursos del sistema (como la pila de llamadas) se consumen. Esto significa que un bucle infinito, como un servidor se puede programar de manera que sólo utiliza las llamadas tail recursivas.
fact(N) when N>1 -> fact(N, N-1);
fact(N) when N=1; N=0 -> 1.
 
fact(F,0) -> F; % The variable F is used as an accumulator
fact(F,N) -> fact(F*N, N-1).
 
 
Ejemplo 1:
 
Construye un lista, imprime la function, aplicando la function a la lista lists:map(). Retorna un valor.
 
counter1() ->
L = lists:seq(1,10),
PrintInt = fun(I) -> io:fwrite("~p ", [I]) end,
lists:foreach(PrintInt, L).
counter3() ->
L = lists:seq(1,10),
[ printInt(I) || I <- L].
 
Ejemplo 2:
 
Recursión con un acumulador. usar un guard sequence ("cuando N > 10") para especificar el caso base. Se define dos funciones "counter4/0" (zero args) y "counter4/1".
 
"counter4/1" consiste en dos clausulas separadas por ;
counter4() -> counter4(1).
counter4(N) when N > 10 -> none;
counter4(N) ->
printInt(N), counter4(N + 1).
 
Ejemplo 3: Procesamiento de la lista con un acumulador
 
counter6() ->
%L = [1,1,1,1,1,1,1,1,1,1],
L = lists:duplicate(10,1),
counter6(L, 0).
counter6([], Acc) -> Acc;
counter6([H|T], Acc) ->
N = H + Acc,
printInt(N),
counter6(T, N).
 
Ejemplo 4:
Procesamieinto de una matriz con recursividad
count_matrix1() ->
count_matrix1( 5, 0, 0 ).
count_matrix1(Size, Size, Size) -> none;
count_matrix1(Size, Size, PtrY) ->
io:format("~n"),
count_matrix1(Size, 0, PtrY+1);
count_matrix1(Size, PtrX, PtrY) ->
io:format("(~p,~p) ", [PtrX, PtrY]),
count_matrix1(Size, PtrX+1, PtrY).
==Declaración, definición y uso de métodos y funciones==
===Métodos y funciones en Erlang===
Para ver una función en Erlang podemos el archivo even_prime.er con el siguiente código
-module(even_prime). % 1
-export([is_even_prime/1]). % 2
% 3
is_even_prime(2) -> % 4 clause 1 is simple
true; % 5
is_even_prime(N) when is_integer(N) -> % 6 clause 2 has a guard: is_integer(N)
false; % 7
is_even_prime(Any) -> % 8 clause 3 is simple
'I prefer integer inputs'. % 9
Las clausulas de las funciones están puestas en el orden en el cual se verifican. Primero is_even_prime(2) es verificada para ver si concuerda. Si el argumento es verificado dicha función regresa verdadero. Si is_even_prime(2) no es verificada, entonces se trata is_even_prime(N). is_even_prime(N) es verificada. La declaración when is_integer es una guardia que solo admite tipos integer a N. al final nos dice que la función se terminad definiendo. is_even_prime(Any) coincide con cualquier cosa de cualquier tipo y regresa “i prefer integer imputs” . la función se ha terminado. Esta función es una función total y cumple todos los los posibles argumentos de entrada.
Salidas
2> c(even_prime).
./even_prime.erl:8: Warning: variable 'Any' is unused
{ok,even_prime}
 
3> even_prime:is_even_prime(2).
true
 
4> even_prime:is_even_prime(1).
false
 
5> even_prime:is_even_prime(seven).
'I prefer integer inputs'
 
===Estructura de una función===
<nowiki>==================================================================</nowiki>
Syntax/structure of a function:
<nowiki>==================================================================</nowiki>
<nowiki>semicolon - ends a clause</nowiki>
<nowiki>period - ends a function </nowiki>
<nowiki>when - starts a guard</nowiki>
<nowiki>arrow - separates the head from the tail of the function</nowiki>
<nowiki>function head - input part of function includes the signature and guard</nowiki>
<nowiki>function tail - output/consequence(s) part of function</nowiki>
<nowiki>signature - the function name and argument structure/count</nowiki>
<nowiki>==================================================================</nowiki>
<nowiki>rotate_list( [H|T] ) when is_atom(H) -> T ++ [H].</nowiki>
.
<nowiki>[----signature-----] [----guard----] . </nowiki>
.
<nowiki>[-----------function head----------] . [--function tail--]</nowiki>
<nowiki>==================================================================</nowiki>
 
==Implementación y uso de la Programación Orientada a Objetos==
===Objetos===
Erlang no tiene explícitamente incorporado características de lenguaje orientado a objetos. Un estilo de programación orientado a objetos se puede lograr por otros medios fácilmente. Es especialmente fácil de hacer programación orientada a objetos, si nos limitamos a la herencia simple. Se puede utilizar procesos para representar a las clases y mensajes para representar a los métodos. Para ello, cada objeto cuando se creó puede crear una cadena de procesos que representan a sus antepasados en la cadena de herencia. Los métodos (mensajes) se puede pasar por la cadena hasta llegar a un proceso que tiene un método de emparejamiento. Si el mensaje llega a la cima de la cadena (la clase objeto o superior que dev_nul), entonces podemos generar una excepción para el “nombre de un método malo". Cada clase (proceso) mantendrá sus propios atributos (variables) en su propia lista recursiva de la llamada de argumentos. Estos atributos se pueden acceder y actualizar con mensajes asi como get y set. Los atributos son almacenados en un diccionario llamado Bundle, en cada proceso de clase.
 
Se incluye un código de ejemplo que crea Programación Orientada a Objetos utilizando la técnica descrita. En el programa, creamos una instancia de la clase entero. Su padre real, sabe cómo tomar la raíz cuadrada de números reales. su padre complejo, sabe cómo tomar la raíz cuadrada de números negativos. Su padre matriz, sabe cómo tomar la raíz cuadrada de una matriz diagonal.
 
Lógicamente, la relación de clase tradicional es sotenida en el diagrama de clase. Un entero es un Real. un Real (float) es un (subconjunto de) complejo. Un número complejo es un (subconjunto de) matrices complejas, si pensamos en una matriz (1 por 1) como un solo número.
 
Un mensaje (método) es enviado a una instancia de un objeto. Si un proceso no sabe cómo hacer algo, pasa un mensaje (método) a su padre (proceso en esta situación). Si tratamos de hacer algo como tomar la raíz cuadrada de una matriz diagonal será pasada hasta dev_nul y generara un error.
 
La función inicial crea una instancia de la clase entero. Después le pregunta a la instancia que calcule la raíz cuadrada de 4 numeros: 4, 0.04, -4 y la matriz [[4,0],[0,9]]. Las respuestas son: 2, 0.2, {2,i} y [[2,0],[0,3]].
 
==Esquema de Administración de Memoria y Separación de Memoria==
Línea 836 ⟶ 1013:
Erlang del “Process Local Heap” realiza una copia para pasar los parámetros de diferentes procesos. Ya que cada proceso tiene una colección diferente es por esta razón la necesidad de una copia. Sin embargo, gracias a que el “Unified Heap” toman la heap como un todo el paso de parámetros mediante procesos no genera un problema como en el “Process Local Heap”.
 
==Implementación de Corutinas==
===Ejemplos Pequeños del Lenguaje===
===Procesos===
Erlang, a diferencia de otros lenguajes funcionales, tiene la capacidad de manejar concurrencia y programación distribuida. Concurrencia es poder manejar varios hilos de ejecución al mismo tiempo. Por ejemplo, sistemas operativos modernos le permitirían utilizar un procesador de textos, una hoja de cálculo, un cliente de correo y un trabajo de impresión todo esto ejecutándose al mismo tiempo. Por supuesto cada procesador (CPU) en el sistema probablemente sólo maneja un hilo (o trabajo) a la vez, pero cambia entre los trabajos a una velocidad que da la ilusión de correrlos todo al mismo tiempo. Es fácil crear hilos paralelos de ejecución en Erlang y es fácil permitir que estos hilos se comuniquen unos con otros. En Erlang a cada hilo de ejecución se le denomina proceso.
Nota: El término "proceso" es utilizado, generalmente, cuando los hilos de ejecución no comparten datos uno con el otro y el termino "hilo" cuando ellos comparten datos de alguna manera. Los hilos de ejecución en Erlang no comparten datos, por eso se llaman procesos.
El BIF (Built-in Function) spawn de Erlang es utilizado para crear un nuevo proceso spawn(Modulo, Funcion_Exportada, Lista de Argumentos)
Ejemplo: (Parte I)
-module(tut14).
 
-export([start/0, say_something/2]).
 
say_something(What, 0) ->
done;
say_something(What, Times) ->
io:format("~p~n", [What]),
say_something(What, Times - 1).
 
start() ->
spawn(tut14, say_something, [hello, 3]),
spawn(tut14, say_something, [goodbye, 3]).
 
 
5> c(tut14).
{ok,tut14}
6> tut14:say_something(hello, 3).
hello
hello
hello
done
Podemos observar que la función say_something escribe su primer argumento la cantidad de veces especificada por el segundo argumento. Ahora si observamos a la función start. Este inicia dos procesos, uno que escribe “hello” tres veces y otro que escribe “goodbye” tres veces. Ambos de los procesos utilizan la función say_something.
Nota: La funcion utilizada por spawn para iniciar un proceso fue exportado desde un modulo (Ej. En el -export al inicio del modulo).
(Parte II)
9> tut14:start().
hello
goodbye
<0.63.0>
hello
goodbye
hello
goodbye
 
Podemos notar que en esta parte no se escribio “hello” tres veces y despues “goodbye” tres veces, en vez el primer proceso escribio “hello” y el Segundo “goodbye”, el siguiente otro “hello”, etc… Ahora, de donde sale el <0.63.0>? Lo que retorna una funcion es el valor de la ultima “cosa” que realize la funcion. Lo ultimo que hace la funcion start es
 
spawn(tut14, say_something, [goodbye, 3]).
spawn retorna el identificador de un proceso (Process Identifier o pid), este identifica de manera unica un proceso.Asi que <0.63.0> es el pid que la función spawn llama.
Nota: Observe que en io:format se utilize ~p en vez de ~w. Esto es asi ya que, ~p escribe la data en una sintaxis estándar, de la misma manera que ~w, sin embargo este rompe los terms que al imprimir son más largos de una línea en varias líneas y las indenta de manera coherente. También intenta detectar listas con caracteres imprimibles e imprimirlos como strings.
 
===Como enviar mensajes===
En el siguiente ejemplo se crean dos procesos que se envían mensajes el uno al otro un determinado número de veces.
Ejemplo:
-module(tut15).
 
-export([start/0, ping/2, pong/0]).
 
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
 
ping(N, Pong_PID) ->
Pong_PID ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_PID).
 
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
 
start() ->
Pong_PID = spawn(tut15, pong, []),
spawn(tut15, ping, [3, Pong_PID]).
 
1> c(tut15).
{ok,tut15}
2> tut15: start().
<0.36.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished
 
La función start primero crea un proceso llamado “pong”:
 
Pong_PID = spawn(tut15, pong, [])
 
Este proceso ejecuta tut15:pong(). Pong_PID es la identidad del proceso “pong”. La funcion start ahora, crea un nuevo proceso llamado “ping”.
 
spawn(tut15, ping, [3, Pong_PID]),
 
este proceso ejecuta
 
tut15:ping(3, Pong_PID)
 
<0.36.0> es el valor de retorno de la función start.
 
El proceso “pong” ahora realiza lo siguiente:
 
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
 
El contructor receive es usado para permitir que los procesos esperen mensajes de otros procesos. Tiene este formato:
 
receive
pattern1 ->
actions1;
pattern2 ->
actions2;
....
patternN
actionsN
end.
 
Nota: antes del end no se utiliza un “;”.
Mensajes entre procesos son validos únicamente entre los tipos de datos establecidos por Erlang. Pueden ser lists, tuples, integers, atoms, pids etc.
Cada proceso tiene su propia cola para los mensajes que recibe. Los nuevos mensajes que recibe, son puestos al final de la cola. Cuando un proceso ejecuta receive, el primer mensaje en la cola es emparejado con el primer patrón en receive, si esto concuerda, el mensaje es removido de la cola y las acciones correspondientes al patrón son ejecutadas.
Sin embargo, si el primer patrón no concuerda, el segundo patrón es probado, si este concuerda, el mensaje es removido de la cola y las acciones correspondientes al segundo patrón son ejecutadas. Si el segundo patrón no concuerda, el tercero es probado, etc. Así será hasta que ya no haya más patrones por probar. Si ya no existen más patrones por probar, el primer mensajes es retenido en la cola y se intenta con el segundo mensaje. Si este concuerda cualquier patrón, las acciones apropiadas son ejecutadas y el segundo mensaje es removido de la cola (manteniendo el primer y el resto de los mensajes en la cola). Si el segundo mensaje no concuerda se prueba el tercer mensaje, etc. Asi será hasta que se llegue al final de la cola. Si se llega al final de la cola, los procesos detienen su ejecucion y esperan hasta que un Nuevo mensaje sea recibido, este procedimiento de repite.
La implementación en Erlang es “ingeniosa” y minimize el numero de veces que cada mensaje es probado contra los patrones en receive.
Continuando con el ejemplo de Ping-Pong:
"Pong" esta esperando mensajes. Si el atom finished es recibido, “pong” imprime “pong finished” y como no tiene nada mas que hacer, finalize. Si recibe un mensaje en este formato:
{ping, Ping_PID}
Imprime "Pong received ping" y envia el atom pong al proceso "ping":
Ping_PID ! pong
 
Nota: El operador “!” es utilizado para enviar mensajes. Su sintaxis es la siguiente:
Pid ! Message
En este caso Message (cualquier termino en Erlang) es enviado al proceso con identidad Pid.
Después de enviar el mensaje pong, al proceso “ping”, “pong” llama a la función pong de nuevo, lo que causa regresar a receive otra vez y esperar otro mensaje. Ahora el proceso “ping” fue iniciado ejecutando:
tut15:ping(3, Pong_PID)
Viendo la función ping/2, observamos que la segunda clausula de ping/2 es ejecutada ya que el valor del primer argumento es 3 (no 0)(la primera clausula es ping(0,Pong_PID), segunda clausula es ping(N,Pong_PID), por consiguiente N se convierte en 3).
La segunda clausula envía un mensaje a “pong”:
Pong_PID ! {ping, self()},
self() retorna el pid del proceso y esto ejecuta self(), en este caso el pid de “ping”. (En el código de “pong” esto caerá en la variable Ping_PID en receive).
"Ping" ahora deberá esperar por la respuesta de “pong”:
receive
pong ->
io:format("Ping received pong~n", [])
end,
y escribe "Ping received pong" cuando esta respuesta haya llegado, después de esto “ping” llama a la función ping una vez más.
ping(N - 1, Pong_PID)
N-1 causa que el primer argumento sea disminuido hasta llegar a 0. Cuando esto ocurre, la primera clausula de ping/2 será ejecutada:
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
El atom finished es enviado a "pong" (causando que el proceso termine) y se imprime "ping finished" en la ventana. "Ping" al no tener nada que hacer se finaliza.
 
==Registrando el Nombre de Procesos==
En el ejemplo anterior, primero se creo "pong" para poder darle la identidad de “pong” a “ping” al ser iniciada. En otras palabras, “ping" debe saber la identidad de “pong” para poder enviarle algún mensaje. Algunas veces los procesos que necesitan saber la identidad de otros son iniciados completamente independientes uno del otro. Erlang provee mecanismos para dale nombres a los procesos y asi utilizar estos nombres como identidades en vez de los pids. Esto se hace utilizando la BIF register:
register(some_atom, Pid)
 
Ahora, si reescribimos el ejemplo de Ping-Pong utilizando esto y dandole el nombre pong al proceso "pong":
-module(tut16).
 
-export([start/0, ping/1, pong/0]).
 
ping(0) ->
pong ! finished,
io:format("ping finished~n", []);
 
ping(N) ->
pong ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1).
 
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
 
start() ->
register(pong, spawn(tut16, pong, [])),
spawn(tut16, ping, [3]).
 
2> c(tut16).
{ok, tut16}
3> tut16:start().
<0.38.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished
 
En la funcion start/0,
register(pong, spawn(tut16, pong, [])),
 
ambos generan el proceso “pong” y les da el nombre pong. En el proceso “ping”, ahora, se puede enviar mensajes a pong de esta manera:
pong ! {ping, self()},
 
y así ping/2 ahora se convierte en ping/1 ya que, no debemos utilizar el argumento Pong_PID.
 
==Manejo de Excepciones==
===Mecanismos de excepciones: catch y throw===
throw(expresión) evalúa la expresión y proporciona el resultado al catch más cercano. Puede usarse para generar un mensaje de error.
(catch expresión) evalúa la expresión, y si captura un mensaje de error genera una estructura de datos, bien la generada por throw o la generada por el propio sistema.
Los mensajes de error proporcionados por el sistema suelen ser bastante descriptivos:
*badarg si se le pasa un argumento incorrecto a una función,
*badarith si se intenta hacer una operación con argumentos inadecuados,
*badmatch si se intenta concordar con un patrón que no encaja,
*case_clause si ninguna cláusula en un case sirve para un argumento dado...
 
===Programación defensiva===
Hay dos formas de corregir errores provocados por la entrada de datos
incorrectos en el sistema:
*Comprobar los datos antes de usarlos.
*No comprobar los datos, sino recuperarse cuando ocurre un error.
La prevención de errores añade código poco útil para el sistema, incrementando el tamaño y la complejidad del mismo. Si la aparición de errores es infrecuente, es preferible no desperdiciar tiempo comprobando cada dato, ahorrando así tiempo en el caso frecuente de que no haya errores.
 
===Reemplazo de código en caliente===
La unidad mínima de reemplazo de código es el módulo. Dos versiones distintas del mismo módulo pueden estar cargadas en memoria al mismo tiempo. Cuando se realiza una llamada a una función se utiliza la última versión disponible del modulo.
 
===Catch and Throw===
Devuelve el valor de expr a menos que se produce una excepción durante la evaluación. En ese caso, se detecta la excepción. Para las excepciones de error de la clase, es decir, errores de ejecución: ( 'EXIT', (Razón, Stack)) es devuelto. Para las excepciones de la salida de clase, que es el código de llamada de salida (término): ( 'EXIT', término) es devuelto. Para las excepciones de la clase de tiro, que es el código de llamada tiro (term): Término se devuelve.
Razón depende del tipo de error que se produjo, y la pila es la pila de llamadas a funciones más recientes, ver los errores y tratamiento de errores.
Tenga en cuenta que tiene una baja prioridad de capturas y capturas subexpresiones a menudo tiene que ser encerrado en un bloque o exprecion en paréntesis:
El BIF tiro (Cualquiera) puede ser utilizado para la retención local de una función. Debe ser evaluado dentro de una captura, que devolverá el valor Any. Ejemplo:
 
Tenga en cuenta que aunque la captura de palabras clave se utiliza en la expresión de tratar, no es una expresión de captura dentro de la expresión intentarlo.
 
===Try===
Devuelve el valor de Exprs (una secuencia de expresiones Expr1, ..., exprn) a menos que se produce una excepción durante la evaluación. En ese caso la excepción es capturada y los patrones de ExceptionPattern con el derecho de clase de excepción de clase son de forma secuencial compara con la excepción capturada. Un omitirse la clase es la abreviatura de tiro. Si un partido tiene éxito y la secuencia opcional de la Guardia ExceptionGuardSeq es cierto, la ExceptionBody correspondiente se evalúa a convertirse en el valor de retorno.
 
Si se produce una excepción durante la evaluación de Exprs pero no hay ExceptionPattern congruencia de la clase de derecho con una secuencia de la guardia real, la excepción se pasa como si Exprs no se había encerrado en una expresión de intentarlo. Si se produce una excepción durante la evaluación de ExceptionBody no es capturado.
 
try Exprs
catch
[nowiki] [Class1 :]ExceptionPattern1 [when ExpresionGuard1] -> [/nowiki]
...ExceptionBody1;
[nowiki] [Class1 :]ExceptionPatternN [when ExpresionGuardN] -> [/nowiki]
ExceptionBodyN
end
 
 
==Ejemplos Pequeños del Lenguaje==
Para entender mejor el lenguaje que mejor manera que con los ejemplos comunes de todos los lenguajes. Y así notar la diferencia entre Erlang y el resto.