Erlang Language
Loop e ricorsione
Ricerca…
Sintassi
- funzione (lista | iolist | tupla) -> funzione (coda).
Osservazioni
Perché le funzioni ricorsive?
Erlang è un linguaggio di programmazione funzionale e non ha alcun tipo di struttura di loop. Tutto nella programmazione funzionale si basa su dati, tipi e funzioni. Se vuoi un ciclo, devi creare una funzione che si chiama da sola.
Il tradizionale while
o for
loop in linguaggio imperativo e orientato agli oggetti può essere rappresentato in questo modo in Erlang:
loop() ->
% do something here
loop().
Un buon metodo per capire questo concetto è quello di espandere tutte le chiamate di funzione. Lo vedremo su altri esempi.
Elenco
Qui la funzione ricorsiva più semplice rispetto al tipo di lista . Questa funzione naviga solo in una lista dall'inizio alla fine e non fa altro.
-spec loop(list()) -> ok.
loop([]) ->
ok;
loop([H|T]) ->
loop(T).
Puoi chiamarlo così:
loop([1,2,3]). % will return ok.
Qui l'espansione della funzione ricorsiva:
loop([1|2,3]) ->
loop([2|3]) ->
loop([3|]) ->
loop([]) ->
ok.
Ciclo ricorsivo con azioni IO
Il codice precedente non fa nulla ed è piuttosto inutile. Quindi, creeremo ora una funzione ricorsiva che esegue alcune azioni. Questo codice è simile agli lists:foreach/2
.
-spec loop(list(), fun()) -> ok.
loop([], _) ->
ok;
loop([H|T], Fun)
when is_function(Fun) ->
Fun(H),
loop(T, Fun).
Puoi chiamarlo in questo modo:
Fun = fun(X) -> io:format("~p", [X]) end.
loop([1,2,3]).
Qui l'espansione della funzione ricorsiva:
loop([1|2,3], Fun(1)) ->
loop([2|3], Fun(2)) ->
loop([3|], Fun(3)) ->
loop([], _) ->
ok.
Puoi confrontare con gli lists:foreach/2
output:
lists:foreach(Fun, [1,2,3]).
Ciclo ricorsivo su lista che restituisce lista modificata
Un altro esempio utile, simile agli lists:map/2
. Questa funzione richiederà una lista e una funzione anonima. Ogni volta che viene abbinato un valore nell'elenco, applichiamo la funzione su di esso.
-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).
Puoi chiamarlo così:
Fun(X) -> X+1 end.
loop([1,2,3], Fun).
Qui l'espansione della funzione ricorsiva:
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].
Questa funzione è anche chiamata "funzione ricorsiva di coda", perché usiamo una variabile come un accumulatore per passare dati modificati su più contesti di esecuzione.
Iolist e Bitstring
Come lista, la funzione più semplice su iolist e bitstring è:
-spec loop(iolist()) -> ok | {ok, iolist} .
loop(<<>>) ->
ok;
loop(<<Head, Tail/bitstring>>) ->
loop(Tail);
loop(<<Rest/bitstring>>) ->
{ok, Rest}
Puoi chiamarlo così:
loop(<<"abc">>).
Qui l'espansione della funzione ricorsiva:
loop(<<"a"/bitstring, "bc"/bitstring>>) ->
loop(<<"b"/bitstring, "c"/bitstring>>) ->
loop(<<"c"/bitstring>>) ->
loop(<<>>) ->
ok.
Funzione ricorsiva su dimensione binaria variabile
Questo codice prende bitstring e ne definisce dinamicamente la dimensione binaria. Quindi, se impostiamo una dimensione di 4
, ogni 4
bit, i dati verranno confrontati. Questo loop non fa nulla di interessante, è solo il nostro pilastro.
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.
Puoi chiamarlo così:
loop(<<"abc">>, 4).
Qui l'espansione della funzione ricorsiva:
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.
Il nostro bitstring è suddiviso su 7 pattern. Perché? Perché per impostazione predefinita, Erlang usa una dimensione binaria di 8
bit, se lo dividiamo in due, abbiamo 4
bit. La nostra stringa è 8*3=24
bit. 24/4=6
modelli. L'ultimo modello è <<>>
. loop/2
funzione loop/2
è chiamata 7 volte.
funzione ricorsiva su dimensione binaria variabile con azioni
Ora possiamo fare qualcosa di più interessante. Questa funzione richiede un altro argomento, una funzione anonima. Ogni volta che abbiniamo un modello, questo verrà passato ad esso.
-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.
Puoi chiamarlo così:
Fun = fun(X) -> io:format("~p~n", [X]) end.
loop(<<"abc">>, 4, Fun).
Qui l'espansione della funzione ricorsiva:
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.
Funzione ricorsiva su bitstring che restituisce bitstring modificato
Questo è simile alle lists:map/2
ma per 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.
Questo codice sembra più complesso. Sono state aggiunte due funzioni: loop/2
e loop/3
. Queste due funzioni sono semplici da interfaccia a loop/4
.
Puoi eseguirlo in questo modo:
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>>
Carta geografica
La mappa in Erlang è l'equivalente di hash in Perl o dizionari in Python, è un archivio di chiavi / valori. Per elencare ogni valore memorizzato in, è possibile elencare ogni chiave e restituire coppia chiave / valore. Questo primo ciclo ti dà un'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).
Puoi eseguirlo in questo modo:
Map = #{1 => "one", 2 => "two", 3 => "three"}.
loop(Map).
% will return:
% 1: "one"
% 2: "two"
% 3: "three"
Stato di gestione
La funzione ricorsiva usa i loro stati per fare il ciclo. Quando si genera un nuovo processo, questo processo sarà semplicemente un ciclo con uno stato definito.
Funzione anonima
Qui 2 esempi di funzioni anonime ricorsive basate sull'esempio precedente. In primo luogo, semplice ciclo infinito:
InfiniteLoop = fun
R() ->
R() end.
In secondo luogo, la funzione anonima esegue una lista di loop over:
LoopOverList = fun
R([]) -> ok;
R([H|T]) ->
R(T) end.
Queste due funzioni potrebbero essere riscritte come:
InfiniteLoop = fun loop/0.
In questo caso, il loop/0
è un riferimento al loop/0
dalle osservazioni. In secondo luogo, con poco più complesso:
LoopOverLlist = fun loop/2.
Qui, loop/2
è un riferimento al loop/2
dell'esempio di lista. Queste due notazioni sono zucchero sintattico.