Не волнуйтесь, что этот код немного сбивает с толку. Это несколько унидиоматично. Мы рассмотрим это через мгновение ...
Перед стилем взгляните на эту первую функцию convert_list_to_k/1
. Это выборочное преобразование объектов из формы, отмеченной l
, в форму, отмеченную k
.
Как он выбирается? Это соответствие по форме и значению первого элемента списка, переданного ему в качестве аргумента. Если он получает значение с типом l
внутри типа {Name, {l, Weight}}
, то выбирается и запускается первое предложение, которое преобразует часть {l, Weight}
в значение {k, Weight}
- я предполагаю, что здесь это «l» для «фунтов» и «k» для «килограммы».
Эта функция выполняет рекурсию по глубине, что обычно не подходит для этого конкретного случая, потому что Erlang (и большинство функциональных языков) оптимизированы для хвостовой рекурсии.
foo([Thing | Things]) ->
NewThing = change(Thing),
[NewThing | foo(Things)];
foo([]) ->
[].
Это в основном то, что делает функция. Это означает, что для любого размера списка необходимо добавить новый уровень стека вызовов, потому что исходный список в первом предложении не может быть возвращен без запоминания каждого промежуточного значения. Это не будет работать с произвольно длинными списками без значительных накладных расходов на память и, как правило, не так.
Представьте себе, что в памяти вы видите это:
foo([change(Thing1) | foo([change(Thing2) | foo([change(Thing3) | ...]]])
Не очень аккуратно. Иногда это правильно, но не в общем случае итерации по списку.
Хвостовая рекурсивная версия будет выглядеть так:
foo(Things) ->
foo(Things, []).
foo([Thing | Things], Accumulator) ->
NewThing = change(Thing),
foo(Things, [NewThing | Accumulator]);
foo([], Accumulator) ->
lists:reverse(Accumulator).
Эта версия работает в постоянном пространстве и является более идиоматической формой явной рекурсии.
Так что насчет всего этого соответствия? Что ж, допустим, я хотел каждый раз печатать значение в килограммах, но некоторые из моих значений указаны в фунтах, а некоторые - в килограммах. Я мог бы обернуть необработанные числовые значения в кортеж и использовать атом для тегирования значений, чтобы я знал, что они означают. Например, такой кортеж, как {pounds, X}
, будет означать, что у меня есть число X, и оно выражено в фунтах, или кортеж {kilos, X}
, который будет означать, что X - это килограммы. Оба по-прежнему в весе.
Так как бы выглядела моя функция?
print_weight({kilos, X}) ->
io:format("Weight is ~wkgs~n", [X]);
print_weight({pounds, X}) ->
Kilos = X / 0.45359237,
io:format("Weight is ~wkgs~n", [Kilos]).
Таким образом, эта функция работает нормально, если ей передается кортеж любого типа.
Как насчет их списка? Мы могли бы сделать явную рекурсию, как указано выше:
print_weights([{kilos, X} | Rest]) ->
ok = io:format("Weight is ~wkgs~n", [X]),
print_weights(Rest);
print_weight([{pounds, X} | Rest]) ->
Kilos = X / 0.45359237,
ok = io:format("Weight is ~wkgs~n", [Kilos]),
print_weights(Rest);
print_weights([]) ->
ok.
Итак, это обрабатывает список значений, как указано выше. Но нам действительно не нужно все это писать, не так ли? У нас уже была функция под названием print_weight/1
, и она уже знает, как выполнять сопоставление. Вместо этого мы могли бы проще определить print_weights/1
как функцию, которая использует операцию со списком:
print_weights(List) ->
lists:foreach(fun print_weight/1, List).
Видите ли, мы обычно не делаем явную рекурсию, когда можем с этим помочь. Причина в том, что в простом случае у нас уже есть функции высшего порядка. сделано для упрощения простого перебора списков. В случае, когда нам нужен побочный эффект и мы не заботимся о возвращаемом значении, например, при выводе весов, как указано выше, мы используем _ 18_.
Возвращаясь к приведенному выше примеру «изменения», если мы уже знаем, что хотим выполнить change/1
для каждого значения, но вернуть ту же карту обратно в неизменном виде, имеет смысл использовать либо понимание списка или _ 20_.
Понимание списка - это специальный синтаксис над картой, который также может включать в себя охранников. Простой случай сопоставления функции с каждым значением в списке и возврата этого списка выглядит следующим образом:
ChangedThings = [change(Thing) || Thing <- Things]
Карта выглядит почти так же, как lists:foreach/2
выше:
ChangedThings = lists:map(fun change/1, Things)
Теперь, возвращаясь к исходному примеру ... может быть, мы хотим обеспечить определенный тип значения. Итак, мы могли бы написать простую функцию, которая делает только это:
ensure_metric({Name, {l, Pounds}}) ->
Kilos = Pounds / 0.45359237,
{Name, {k, Kilos}};
ensure_metric(Value = {_, {k, _}}) ->
Value.
Это все, что нам нужно. Что происходит выше, так это то, что любой кортеж формы {Foo, {l, Bar}}
соответствует первому предложению и преобразуется операцией в этом предложении, а затем переупаковывается в форму {Foo, {k, Baz}
, а любой кортеж формы {Foo, {k, Bar}}
совпадает со вторым, но передается без изменения . Теперь мы можем просто сопоставить эту функцию со списком:
convert_list_to_k(List) ->
lists:map(fun ensure_metric/1, List).
Намного проще рассуждать только об одной функции за раз!
Функция min / max немного безумна. Мы не хотели бы писать if
, если бы у нас не был полностью ограниченный математический случай. Например:
if
X > Y -> option1();
X =:= Y -> option2();
X == Y -> option3();
X < Y -> option4()
end,
Это четыре теста в одном предложении. Иногда использование if
имеет для этого смысл. Однако чаще вы получаете то, что у вас было выше, где происходит простое сравнение. В этом случае case
гораздо выразительнее:
case X > Y ->
true -> do_something();
false -> something_else()
end,
НО! Может быть, то, что мы действительно хотим в функции min / max, - это просто управлять охранниками и избегать написания сложной логики тела. Вот тот, который работает с простым списком чисел, небольшое изменение приведет к тому, что он будет соответствовать типу данных, с которым вы имеете дело (эти кортежи):
min_max([Number | Numbers]) ->
min_max(Numbers, Number, Number).
min_max([N | Ns], Min, Max) when N < Min ->
min_max(Ns, N, Max);
min_max([N | Ns], Min, Max) when N > Max ->
min_max(Ns, Min, N);
min_max([_ | Ns], Min, Max) ->
min_max(Ns, Min, Max);
min_max([], Min, Max) ->
{Min, Max}.
В процедурной логике здесь не требуется много подбрасываний гепарда.
Эрланг настолько скучно прост и крошечный, как язык, что, как только ненужность большей части процедурной логики проникает в вас, вы просто внезапно "получаете новые глаза". Несколько связанных вопросов и ответов со справочной информацией могут быть полезны в вашем путешествии:
person
zxq9
schedule
29.10.2017
convert_list_to_k([{Name, {l, Weight}} | Rest])
- это такой цикл for, который вычитает 1 изRest
и может что-то делать? - person hemantmetal   schedule 28.10.2017