Erlang Language
Bucle y recursion
Buscar..
Sintaxis
- función (lista | iolist | tupla) -> función (cola).
Observaciones
¿Por qué las funciones recursivas?
Erlang es un lenguaje de programación funcional y no tiene ningún tipo de estructura de bucle. Todo en la programación funcional se basa en datos, tipo y funciones. Si desea un bucle, necesita crear una función que se llame a sí misma.
El while
tradicional o for
bucle en lenguaje imperativo y orientado a objetos se puede representar así en Erlang:
loop() ->
% do something here
loop().
Un buen método para entender este concepto es expandir todas las llamadas de función. Lo veremos en otros ejemplos.
Lista
Aquí la función recursiva más simple sobre el tipo de lista . Esta función solo navega en una lista desde el principio hasta el final y no hace nada más.
-spec loop(list()) -> ok.
loop([]) ->
ok;
loop([H|T]) ->
loop(T).
Puedes llamarlo así:
loop([1,2,3]). % will return ok.
Aquí la expansión de la función recursiva:
loop([1|2,3]) ->
loop([2|3]) ->
loop([3|]) ->
loop([]) ->
ok.
Bucle recursivo con acciones IO.
El código anterior no hace nada, y es bastante inútil. Entonces, crearemos ahora una función recursiva que ejecutará algunas acciones. Este código es similar a las lists:foreach/2
.
-spec loop(list(), fun()) -> ok.
loop([], _) ->
ok;
loop([H|T], Fun)
when is_function(Fun) ->
Fun(H),
loop(T, Fun).
Puedes llamarlo así:
Fun = fun(X) -> io:format("~p", [X]) end.
loop([1,2,3]).
Aquí la expansión de la función recursiva:
loop([1|2,3], Fun(1)) ->
loop([2|3], Fun(2)) ->
loop([3|], Fun(3)) ->
loop([], _) ->
ok.
Puedes comparar con lists:foreach/2
output:
lists:foreach(Fun, [1,2,3]).
Bucle recursivo sobre lista que devuelve lista modificada
Otro ejemplo útil, similar a las lists:map/2
. Esta función tomará una lista y una función anónima. Cada vez que un valor en la lista coincide, aplicamos la función en él.
-spec loop(A :: list(), fun()) -> list().
loop(List, Fun)
when is_list(List), is_function(Fun) ->
loop(List, Fun, []).
-spec loop(list(), fun(), list()) -> list() | {error, list()}.
loop([], _, Buffer)
when is_list(Buffer) ->
lists:reverse(Buffer);
loop([H|T], Fun, Buffer)
when is_function(Fun), is_list(Buffer) ->
BufferReturn = [Fun(H)] ++ Buffer,
loop(T, Fun, BufferReturn).
Puedes llamarlo así:
Fun(X) -> X+1 end.
loop([1,2,3], Fun).
Aquí la expansión de la función recursiva:
loop([1|2,3], Fun(1), [2]) ->
loop([2|3], Fun(2), [3,2]) ->
loop([3|], Fun(3), [4,3,2]) ->
loop([], _, [4,3,2]) ->
list:reverse([4,3,2]) ->
[2,3,4].
Esta función también se denomina "función recursiva de cola", porque usamos una variable como un acumulador para pasar datos modificados sobre un contexto de ejecución múltiple.
Iolist y Bitstring
Lista de Me gusta, la función más simple sobre iolist y la cadena de bits es:
-spec loop(iolist()) -> ok | {ok, iolist} .
loop(<<>>) ->
ok;
loop(<<Head, Tail/bitstring>>) ->
loop(Tail);
loop(<<Rest/bitstring>>) ->
{ok, Rest}
Puedes llamarlo así:
loop(<<"abc">>).
Aquí la expansión de la función recursiva:
loop(<<"a"/bitstring, "bc"/bitstring>>) ->
loop(<<"b"/bitstring, "c"/bitstring>>) ->
loop(<<"c"/bitstring>>) ->
loop(<<>>) ->
ok.
Función recursiva sobre tamaño binario variable.
Este código toma la cadena de bits y define dinámicamente su tamaño binario. Así que si, si establecemos un tamaño de 4
, cada 4
bits, se emparejará un dato. Este bucle no hace nada interesante, es solo nuestro pilar.
loop(Bitstring, Size)
when is_bitstring(Bitstring), is_integer(Size) ->
case Bitstring of
<<>> ->
ok;
<<Head:Size/bitstring,Tail/bitstring>> ->
loop(Tail, Size);
<<Rest/bitstring>> ->
{ok, Rest}
end.
Puedes llamarlo así:
loop(<<"abc">>, 4).
Aquí la expansión de la función recursiva:
loop(<<6:4/bitstring, 22, 38, 3:4>>, 4) ->
loop(<<1:4/bitstring, "bc">>, 4) ->
loop(<<6:4/bitstring, 38,3:4>>, 4) ->
loop(<<2:4/bitstring, "c">>, 4) ->
loop(<<6:4/bitstring, 3:4>>, 4) ->
loop(<<3:4/bitstring>>, 4) ->
loop(<<>>, 4) ->
ok.
Nuestra cadena de bits está dividida en 7 patrones. ¿Por qué? Porque por defecto, Erlang usa un tamaño binario de 8
bits, si lo dividimos en dos, tenemos 4
bits. Nuestra cadena es 8*3=24
bits. 24/4=6
patrones. El último patrón es <<>>
. loop/2
se llama 7 veces.
Función recursiva sobre tamaño binario variable con acciones.
Ahora, podemos hacer algo más interesante. Esta función toma un argumento más, una función anónima. Cada vez que coincidimos con un patrón, éste será pasado a él.
-spec loop(iolist(), integer(), function()) -> ok.
loop(Bitstring, Size, Fun) ->
when is_bitstring(Bitstring), is_integer(Size), is_function(Fun) ->
case Bitstring of
<<>> ->
ok;
<<Head:Size/bitstring,Tail/bitstring>> ->
Fun(Head),
loop(Tail, Size, Fun);
<<Rest/bitstring>> ->
Fun(Rest),
{ok, Rest}
end.
Puedes llamarlo así:
Fun = fun(X) -> io:format("~p~n", [X]) end.
loop(<<"abc">>, 4, Fun).
Aquí la expansión de la función recursiva:
loop(<<6:4/bitstring, 22, 38, 3:4>>, 4, Fun(<<6:4>>) ->
loop(<<1:4/bitstring, "bc">>, 4, Fun(<<1:4>>)) ->
loop(<<6:4/bitstring, 38,3:4>>, 4, Fun(<<6:4>>)) ->
loop(<<2:4/bitstring, "c">>, 4, Fun(<<2:4>>)) ->
loop(<<6:4/bitstring, 3:4>>, 4, Fun(<<6:4>>) ->
loop(<<3:4/bitstring>>, 4, Fun(<<3:4>>) ->
loop(<<>>, 4) ->
ok.
Función recursiva sobre la cadena de bits que devuelve la cadena de bits modificada
Éste es similar a las lists:map/2
pero para bitstring e iolist.
% public function (interface).
-spec loop(iolist(), fun()) -> iolist() | {iolist(), iolist()}.
loop(Bitstring, Fun) ->
loop(Bitstring, 8, Fun).
% public function (interface).
-spec loop(iolist(), integer(), fun()) -> iolist() | {iolist(), iolist()}.
loop(Bitstring, Size, Fun) ->
loop(Bitstring, Size, Fun, <<>>)
% private function.
-spec loop(iolist(), integer(), fun(), iolist()) -> iolist() | {iolist(), iolist()}.
loop(<<>>, _, _, Buffer) ->
Buffer;
loop(Bitstring, Size, Fun, Buffer) ->
when is_bitstring(Bitstring), is_integer(Size), is_function(Fun) ->
case Bitstring of
<<>> ->
Buffer;
<<Head:Size/bitstring,Tail/bitstring>> ->
Data = Fun(Head),
BufferReturn = <<Buffer/bitstring, Data/bitstring>>,
loop(Tail, Size, Fun, BufferReturn);
<<Rest/bitstring>> ->
{Buffer, Rest}
end.
Este código parece más complejo. Se agregaron dos funciones: loop/2
y loop/3
. Estas dos funciones son interfaz simple a loop/4
.
Puedes ejecutarlo así:
Fun = fun(<<X>>) -> << (X+1) >> end.
loop(<<"abc">>, Fun).
% will return <<"bcd">>
Fun = fun(<<X:4>>) -> << (X+1) >> end.
loop(<<"abc">>, 4, Fun).
% will return <<7,2,7,3,7,4>>
loop(<<"abc">>, 4, Fun, <<>>).
% will return <<7,2,7,3,7,4>>
Mapa
El mapa en Erlang es equivalente a hashes en Perl o diccionarios en Python, es un almacén de clave / valor. Para enumerar cada valor almacenado en, puede enumerar cada clave y devolver el par clave / valor. Este primer bucle te dará una idea:
loop(Map) when is_map(Map) ->
Keys = maps:keys(Map),
loop(Map, Keys).
loop(_ , []) ->
ok;
loop(Map, [Head|Tail]) ->
Value = maps:get(Head, Map),
io:format("~p: ~p~n", [Head, Value]),
loop(Map, Tail).
Puedes ejecutarlo así:
Map = #{1 => "one", 2 => "two", 3 => "three"}.
loop(Map).
% will return:
% 1: "one"
% 2: "two"
% 3: "three"
Estado gestor
La función recursiva usa sus estados para hacer un bucle. Cuando genera un nuevo proceso, este proceso será simplemente un bucle con un estado definido.
Función anónima
Aquí 2 ejemplos de funciones anónimas recursivas basadas en el ejemplo anterior. En primer lugar, simple bucle infinito:
InfiniteLoop = fun
R() ->
R() end.
En segundo lugar, la función anónima haciendo loop over list:
LoopOverList = fun
R([]) -> ok;
R([H|T]) ->
R(T) end.
Estas dos funciones pueden ser reescritas como:
InfiniteLoop = fun loop/0.
En este caso, loop/0
es una referencia al loop/0
de los comentarios. En segundo lugar, con poco más complejo:
LoopOverLlist = fun loop/2.
Aquí, loop/2
es una referencia a loop/2
del ejemplo de lista. Estas dos notaciones son azúcares sintácticos.