Erlang Language
Pętla i rekurencja
Szukaj…
Składnia
- funkcja (lista | iolist | krotka) -> funkcja (ogon).
Uwagi
Dlaczego funkcje rekurencyjne?
Erlang jest funkcjonalnym językiem programowania i nie ma struktury pętli. Wszystko w programowaniu funkcjonalnym opiera się na danych, typie i funkcjach. Jeśli chcesz uzyskać pętlę, musisz utworzyć funkcję, która się nazywa.
Tradycyjna pętla while
lub for
w języku rozkazującym i obiektowym może być reprezentowana w Erlang w następujący sposób:
loop() ->
% do something here
loop().
Dobrym sposobem na zrozumienie tej koncepcji jest rozszerzenie wszystkich wywołań funkcji. Zobaczymy to na innych przykładach.
Lista
Tutaj najprostsza funkcja rekurencyjna nad typem listy . Ta funkcja nawiguje tylko po liście od początku do końca i nic więcej nie robi.
-spec loop(list()) -> ok.
loop([]) ->
ok;
loop([H|T]) ->
loop(T).
Możesz to tak nazwać:
loop([1,2,3]). % will return ok.
Tutaj rozwinięcie funkcji rekurencyjnej:
loop([1|2,3]) ->
loop([2|3]) ->
loop([3|]) ->
loop([]) ->
ok.
Pętla rekurencyjna z akcjami IO
Poprzedni kod nic nie robi i jest całkiem bezużyteczny. Dlatego stworzymy teraz funkcję rekurencyjną, która wykona niektóre akcje. Ten kod jest podobny do lists:foreach/2
.
-spec loop(list(), fun()) -> ok.
loop([], _) ->
ok;
loop([H|T], Fun)
when is_function(Fun) ->
Fun(H),
loop(T, Fun).
Możesz to nazwać w następujący sposób:
Fun = fun(X) -> io:format("~p", [X]) end.
loop([1,2,3]).
Tutaj rozwinięcie funkcji rekurencyjnej:
loop([1|2,3], Fun(1)) ->
loop([2|3], Fun(2)) ->
loop([3|], Fun(3)) ->
loop([], _) ->
ok.
Możesz porównać z lists:foreach/2
output:
lists:foreach(Fun, [1,2,3]).
Pętla rekurencyjna nad listą zwraca zmodyfikowaną listę
Kolejny przydatny przykład, podobny do lists:map/2
. Ta funkcja przyjmuje jedną listę i jedną anonimową funkcję. Za każdym razem, gdy wartość na liście jest dopasowywana, stosujemy na niej funkcję.
-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).
Możesz to tak nazwać:
Fun(X) -> X+1 end.
loop([1,2,3], Fun).
Tutaj rozwinięcie funkcji rekurencyjnej:
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].
Ta funkcja jest również nazywana „funkcją rekurencyjną ogona”, ponieważ używamy zmiennej, takiej jak akumulator, do przekazywania zmodyfikowanych danych w kontekście wielokrotnego wykonywania.
Iolist i Bitstring
Podobnie jak lista, najprostszą funkcją nad iolist i ciągiem bitów jest:
-spec loop(iolist()) -> ok | {ok, iolist} .
loop(<<>>) ->
ok;
loop(<<Head, Tail/bitstring>>) ->
loop(Tail);
loop(<<Rest/bitstring>>) ->
{ok, Rest}
Możesz to tak nazwać:
loop(<<"abc">>).
Tutaj rozwinięcie funkcji rekurencyjnej:
loop(<<"a"/bitstring, "bc"/bitstring>>) ->
loop(<<"b"/bitstring, "c"/bitstring>>) ->
loop(<<"c"/bitstring>>) ->
loop(<<>>) ->
ok.
Funkcja rekurencyjna na zmiennym rozmiarze binarnym
Ten kod przyjmuje łańcuch bitów i dynamicznie definiuje jego rozmiar binarny. Jeśli więc ustawimy rozmiar 4
, co 4
bity, dane zostaną dopasowane. Ta pętla nie robi nic interesującego, to tylko nasz filar.
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.
Możesz to tak nazwać:
loop(<<"abc">>, 4).
Tutaj rozwinięcie funkcji rekurencyjnej:
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.
Nasz ciąg bitów jest podzielony na 7 wzorów. Dlaczego? Ponieważ domyślnie Erlang używa binarnego rozmiaru 8
bitów, jeśli podzielimy go na dwa, otrzymamy 4
bity. Nasz ciąg ma 8*3=24
bity. 24/4=6
wzorów. Ostatni wzór to <<>>
. Funkcja loop/2
jest wywoływana 7 razy.
funkcja rekurencyjna nad zmiennym rozmiarem binarnym z akcjami
Teraz możemy zrobić coś ciekawszego. Ta funkcja wymaga jeszcze jednego argumentu, funkcji anonimowej. Za każdym razem, gdy dopasujemy wzór, ten zostanie do niego przekazany.
-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.
Możesz to tak nazwać:
Fun = fun(X) -> io:format("~p~n", [X]) end.
loop(<<"abc">>, 4, Fun).
Tutaj rozwinięcie funkcji rekurencyjnej:
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.
Funkcja rekurencyjna nad ciągiem bitów zwraca zmodyfikowany ciąg bitów
Ten jest podobny do lists:map/2
ale do bitstringów i 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.
Ten kod wydaje się bardziej złożony. Dodano dwie funkcje: loop/2
i loop/3
. Te dwie funkcje to prosty interfejs do loop/4
.
Możesz wykonać to w następujący sposób:
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
Mapa w Erlang jest odpowiednikiem skrótów w Perlu lub słowników w Pythonie, jest to magazyn kluczy / wartości. Aby wyświetlić listę wszystkich przechowywanych wartości, możesz wymienić każdy klucz i zwrócić parę klucz / wartość. Ta pierwsza pętla daje pomysł:
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).
Możesz to wykonać w następujący sposób:
Map = #{1 => "one", 2 => "two", 3 => "three"}.
loop(Map).
% will return:
% 1: "one"
% 2: "two"
% 3: "three"
Państwo zarządzające
Funkcje rekurencyjne używają swoich stanów do zapętlania. Kiedy spawnujesz nowy proces, proces ten będzie po prostu pętlą o określonym stanie.
Funkcja anonimowa
Tutaj 2 przykłady rekurencyjnych funkcji anonimowych na podstawie poprzedniego przykładu. Po pierwsze, prosta nieskończona pętla:
InfiniteLoop = fun
R() ->
R() end.
Po drugie, anonimowa funkcja robi pętlę nad listą:
LoopOverList = fun
R([]) -> ok;
R([H|T]) ->
R(T) end.
Te dwie funkcje można przepisać jako:
InfiniteLoop = fun loop/0.
W tym przypadku loop/0
jest odwołaniem do loop/0
z uwag. Po drugie, z nieco bardziej złożonymi:
LoopOverLlist = fun loop/2.
Tutaj loop/2
jest odniesieniem do loop/2
z przykładu listy. Te dwie notacje to cukier składniowy.