了解这个Erlang代码是什么?
根本不熟悉Erlang,但我试图解释这段代码的作用?
以下是我对代码的理解。 任何帮助都是有用的。 我在看教程,但传递的值在这种情况下令人困惑。
example_ convert_list_to_k([{Name, {l, Weight}} | Rest]) //{1,Weight} <- This one
那么在convert_list_to_k
返回的值如何?
让我们来说说这个功能块
convert_list_to_k([{Name, {l, Weight}} | Rest]) ->
Converted_Object = {Name, {k, Weight / 0.45359237}},
[Converted_Object | convert_list_to_k(Rest)];
convert_list_to_k([Object | Rest]) ->
[Object | convert_list_to_k(Rest)];
convert_list_to_k([]) ->
[].
以下是带解释的代码。
-module(erlang_program).
-export([format_weight/1]).
在上面的导出中,/ 1表示它将接收一个属性(我不知道哪个属性)
format_weight(List_of_objects) ->
Converted_List = convert_list_to_k(List_of_objects),
print_weight(Converted_List),
{Max_object, Min_object} = find_max_and_min(Converted_List),
print_max_and_min(Max_object, Min_object).
将导入convert_list_to_k
, print_weight(Converted_List)
, find_max_and_min(Converted_List)
和print_max_and_min(Max_object, Min_object).
的主函数print_max_and_min(Max_object, Min_object).
根据我的理解,它正在做以下事情:
我对[{Name, {l, Weight}} | Rest]
的方式感到困惑 [{Name, {l, Weight}} | Rest]
通过
convert_list_to_k([{Name, {l, Weight}} | Rest]) ->
Converted_Object = {Name, {k, Weight / 0.45359237}},
[Converted_Object | convert_list_to_k(Rest)];
convert_list_to_k([Object | Rest]) ->
[Object | convert_list_to_k(Rest)];
convert_list_to_k([]) ->
[].
print_weight([{Name, {k, Weight}} | Rest]) ->
io:format("~-15w ~w c~n", [Name, Weight]),
print_weight(Rest);
print_weight([]) ->
ok.
find_max_and_min([Object | Rest]) ->
find_max_and_min(Rest, Object, Object).
find_max_and_min([{Name, {k, Weight}} | Rest],
{Max_Name, {k, Max_Weight}},
{Min_Name, {k, Min_Weight}}) ->
if
Weight > Max_Weight ->
Max_Object = {Name, {k, Weight}};
true ->
Max_Object = {Max_Name, {k, Max_Weight}}
end,
if
Weight < Min_Weight ->
Min_Object = {Name, {k, Weight}};
true ->
Min_Object = {Min_Name, {k, Min_Weight}}
end,
find_max_and_min(Rest, Max_Object, Min_Object);
find_max_and_min([], Max_Object, Min_Object) ->
{Max_Object, Min_Object}.
print_max_and_min({Max_name, {k, Max_object}}, {Min_name, {k, Min_object}}) ->
io:format("Max weight was ~w c in ~w~n", [Max_object, Max_name]),
io:format("Min weight was ~w c in ~w~n", [Min_object, Min_name]).
不要担心这段代码有点混乱。 它有点单一。 我们稍后会解决这个问题......
在样式之前,先看看第一个函数convert_list_to_k/1
。 它有选择地将对象从标有l
的窗体转换为标有k
的窗体。
它如何选择? 它将传递给它的列表的第一个元素的形状和值作为参数进行匹配。 如果它接收到一个类型为{Name, {l, Weight}}
的l
类型值的值{Name, {l, Weight}}
则选择并运行第一个子句,将{l, Weight}
部分转换为{k, Weight}
值 - I假设这里的“磅”是“磅”,“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).
看,我们通常不会做明确的递归,当我们可以帮助它时。 原因在于,在简单的情况下,我们已经具有更高阶的函数来简化对列表的简单迭代。 在我们需要副作用而不关心返回值的情况下,如上面打印权重时,我们使用lists:foreach/2
。
回到上面的“更改”示例,如果我们已经知道我们想要对每个值执行change/1
,但完整地返回相同的映射,则使用列表理解或lists:map/2
更有意义lists:map/2
。
列表理解是地图上的特殊语法,也可以包括警卫。 将函数映射到列表中的每个值并返回该列表的简单情况如下所示:
ChangedThings = [change(Thing) || Thing <- Things]
一张地图看起来几乎完全是lists:foreach/2
的方式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}}
元组匹配第二个{Foo, {k, Bar}}
但不会被更改。 现在我们可以简单地将该函数映射到一个列表上:
convert_list_to_k(List) ->
lists:map(fun ensure_metric/1, List).
更容易推断一次只有一个功能!
最小/最大功能有点疯狂。 除非我们有一个完全有限的数学例子,否则我们不想写一个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}.
程序逻辑中不需要大量的猎豹翻转。
Erlang的语言非常简单而且小巧,一旦大多数程序逻辑的不必要性突然消失在你身上,就会突然“变得新眼睛”。 一些与背景信息相关的Q / A可能对您的旅程有所帮助: