Erlang Language
Петля и рекурсия
Поиск…
Синтаксис
- function (list | iolist | tuple) -> function (tail).
замечания
Почему рекурсивные функции?
Erlang - это функциональный язык программирования и не имеет никакой структуры циклов. Все функциональное программирование основано на данных, типе и функциях. Если вам нужен цикл, вам нужно создать функцию, которая сама вызывает вызов.
Традиционный while
или for
loop в императивном и объектно-ориентированном языке может быть представлен таким же образом в Erlang:
loop() ->
% do something here
loop().
Хорошим методом понимания этой концепции является расширение всех вызовов функций. Мы увидим это на других примерах.
Список
Здесь простейшая рекурсивная функция над типом списка . Эта функция только переходит в список от начала до конца и больше ничего не делает.
-spec loop(list()) -> ok.
loop([]) ->
ok;
loop([H|T]) ->
loop(T).
Вы можете назвать это следующим образом:
loop([1,2,3]). % will return ok.
Здесь разложение рекурсивной функции:
loop([1|2,3]) ->
loop([2|3]) ->
loop([3|]) ->
loop([]) ->
ok.
Рекурсивный цикл с действиями IO
Предыдущий код ничего не делает и довольно бесполезен. Итак, мы создадим теперь рекурсивную функцию, выполняющую некоторые действия. Этот код похож на lists:foreach/2
.
-spec loop(list(), fun()) -> ok.
loop([], _) ->
ok;
loop([H|T], Fun)
when is_function(Fun) ->
Fun(H),
loop(T, Fun).
Вы можете вызвать его так:
Fun = fun(X) -> io:format("~p", [X]) end.
loop([1,2,3]).
Здесь разложение рекурсивной функции:
loop([1|2,3], Fun(1)) ->
loop([2|3], Fun(2)) ->
loop([3|], Fun(3)) ->
loop([], _) ->
ok.
Вы можете сравнить со lists:foreach/2
output:
lists:foreach(Fun, [1,2,3]).
Рекурсивный цикл над списком, возвращающим измененный список
Другой полезный пример, аналогичный lists:map/2
. Эта функция будет принимать один список и одну анонимную функцию. Каждый раз, когда значение в списке сопоставляется, мы применяем к нему функцию.
-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).
Вы можете назвать это следующим образом:
Fun(X) -> X+1 end.
loop([1,2,3], Fun).
Здесь разложение рекурсивной функции:
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].
Эта функция также называется «хвостовой рекурсивной функцией», потому что мы используем переменную, подобную аккумулятору, для передачи измененных данных по нескольким контекстам выполнения.
Iolist и битстрим
Как и список, простейшая функция над iolist и bitstring :
-spec loop(iolist()) -> ok | {ok, iolist} .
loop(<<>>) ->
ok;
loop(<<Head, Tail/bitstring>>) ->
loop(Tail);
loop(<<Rest/bitstring>>) ->
{ok, Rest}
Вы можете назвать это следующим образом:
loop(<<"abc">>).
Здесь разложение рекурсивной функции:
loop(<<"a"/bitstring, "bc"/bitstring>>) ->
loop(<<"b"/bitstring, "c"/bitstring>>) ->
loop(<<"c"/bitstring>>) ->
loop(<<>>) ->
ok.
Рекурсивная функция по переменному двоичному размеру
Этот код принимает bitstring и динамически определяет двоичный размер. Поэтому, если, если мы установим размер 4
, каждые 4
бита, данные будут сопоставлены. Этот цикл не делает ничего интересного, его просто наш столп.
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.
Вы можете назвать это следующим образом:
loop(<<"abc">>, 4).
Здесь разложение рекурсивной функции:
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.
Наша битстрима разделена на 7 шаблонов. Зачем? Поскольку по умолчанию Erlang использует двоичный размер 8
бит, если мы разделим его на две части, у нас есть 4
бита. Наша строка 8*3=24
бит. 24/4=6
рисунков. Последний шаблон - <<>>
. Функция loop/2
называется 7 раз.
рекурсивная функция по переменному двоичному размеру с действиями
Теперь мы можем сделать более интересную вещь. Эта функция принимает еще один аргумент - анонимную функцию. Каждый раз, когда мы сопоставляем шаблон, он будет передан ему.
-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.
Вы можете назвать это следующим образом:
Fun = fun(X) -> io:format("~p~n", [X]) end.
loop(<<"abc">>, 4, Fun).
Здесь разложение рекурсивной функции:
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.
Рекурсивная функция над битовой строкой, возвращающая измененную bitstring
Это похоже на lists:map/2
но для bitstring и 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.
Этот код кажется более сложным. Добавлены две функции: loop/2
и loop/3
. Эти две функции - простой интерфейс для loop/4
.
Вы можете выполнить его следующим образом:
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>>
карта
Карта в Erlang эквивалентна хэшам в Perl или словарям в Python, его хранилище ключей / значений. Чтобы перечислить каждое сохраненное значение, вы можете перечислить каждый ключ и вернуть пару ключ / значение. Этот первый цикл дает вам представление:
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).
Вы можете выполнить его так:
Map = #{1 => "one", 2 => "two", 3 => "three"}.
loop(Map).
% will return:
% 1: "one"
% 2: "two"
% 3: "three"
Управляющее государство
Рекурсивная функция использует свои состояния в цикле. Когда вы создаете новый процесс, этот процесс будет просто циклом с некоторым определенным состоянием.
Анонимная функция
Вот 2 примера рекурсивных анонимных функций, основанных на предыдущем примере. Во-первых, простой бесконечный цикл:
InfiniteLoop = fun
R() ->
R() end.
Во-вторых, анонимная функция делает цикл над списком:
LoopOverList = fun
R([]) -> ok;
R([H|T]) ->
R(T) end.
Эти две функции можно переписать следующим образом:
InfiniteLoop = fun loop/0.
В этом случае loop/0
является ссылкой на loop/0
из замечаний. Во-вторых, с немного более сложными:
LoopOverLlist = fun loop/2.
Здесь loop/2
является ссылкой на loop/2
из примера списка. Эти две записи являются синтаксическим сахаром.