理解这个 Erlang 代码是什么?
Understanding what this Erlang Code is?
完全不熟悉 Erlang,但我正在尝试解释这段代码的作用?
以下是我对代码的理解。任何帮助都会有用。
我正在查看教程,但在这种情况下传递的值令人困惑。
示例- 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]).
上面export中的/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).
据我了解,它正在做以下事情:
- 将对象列表转换为某种格式
- 打印转换后的列表
- 找到最大值和最小值,并将其放入对象最大值和最小值
- 打印最大和最小对象
我对 [{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
.
的形式
选的怎么样?它是 matching on the shape and value 作为传递给它的列表的第一个元素争论。如果它接收到一个内部带有 l
类型值的值,例如 {Name, {l, Weight}}
,则选择第一个子句并 运行,这会将 {l, Weight}
部分转换为 {k, Weight}
值——我假设这里是 "l" for "pounds" 和 "k" for "kilograms".
此函数正在执行 深度递归,这通常不适合这种特殊情况,因为 Erlang(和大多数函数式语言)对 进行了优化尾递归.
foo([Thing | Things]) ->
NewThing = change(Thing),
[NewThing | foo(Things)];
foo([]) ->
[].
这基本上就是函数的作用。这意味着无论列表的大小如何,都必须添加调用堆栈的新层,因为如果不记住,第一个子句中的原始列表就无法returned中间值。如果没有显着的内存开销,这将不适用于任意长的列表,并且通常不是这样工作的。
想象一下在记忆中看到这个:
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).
此版本 运行s 在 常量 space 中,是显式递归的更惯用形式。
那么所有匹配的东西呢?好吧,假设我想每次都打印一个以千克为单位的值,但我的一些值以磅为单位,一些以公斤为单位。我可以将原始数值包装在一个元组中,并使用一个原子来 标记 这些值,这样我就知道它们的意思了。例如,像 {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).
看,我们通常不会在可以帮助的情况下进行显式递归。原因是在简单的情况下我们已经有higher-order functions made to simplify simple iteration over lists. In the case where we want a side effect and don't care about the return value, like printing the weights as above, we use lists:foreach/2
.
回到上面的"change"例子,如果我们已经知道我们要对每个值执行change/1
,但是return原样返回相同的映射,它使更多感觉要么使用 list comprehension or lists:map/2
.
列表理解是映射的一种特殊语法,它也可以包括守卫。将函数映射到列表中的每个值并 returning 该列表的简单情况如下所示:
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}
形式,以及 [=40 形式的任何元组=] 与第二个匹配,但不加改变地传递。我们现在可以简单地将函数映射到一个列表中:
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}.
这里的程序逻辑不需要太多的猎豹翻转。
Erlang 简单得令人厌烦 并且作为一种语言非常微小,一旦大多数过程逻辑的不必要性让你突然陷入其中 "get new eyes"。一些相关的 Q/As 背景信息可能对您的旅程有所帮助:
- Erlang Recursive end loop
- How does the recursive call work in this erlang function?
- Explanation of lists:fold function
完全不熟悉 Erlang,但我正在尝试解释这段代码的作用?
以下是我对代码的理解。任何帮助都会有用。 我正在查看教程,但在这种情况下传递的值令人困惑。
示例- 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]).
上面export中的/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).
据我了解,它正在做以下事情:
- 将对象列表转换为某种格式
- 打印转换后的列表
- 找到最大值和最小值,并将其放入对象最大值和最小值
- 打印最大和最小对象
我对 [{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
.
选的怎么样?它是 matching on the shape and value 作为传递给它的列表的第一个元素争论。如果它接收到一个内部带有 l
类型值的值,例如 {Name, {l, Weight}}
,则选择第一个子句并 运行,这会将 {l, Weight}
部分转换为 {k, Weight}
值——我假设这里是 "l" for "pounds" 和 "k" for "kilograms".
此函数正在执行 深度递归,这通常不适合这种特殊情况,因为 Erlang(和大多数函数式语言)对 进行了优化尾递归.
foo([Thing | Things]) ->
NewThing = change(Thing),
[NewThing | foo(Things)];
foo([]) ->
[].
这基本上就是函数的作用。这意味着无论列表的大小如何,都必须添加调用堆栈的新层,因为如果不记住,第一个子句中的原始列表就无法returned中间值。如果没有显着的内存开销,这将不适用于任意长的列表,并且通常不是这样工作的。
想象一下在记忆中看到这个:
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).
此版本 运行s 在 常量 space 中,是显式递归的更惯用形式。
那么所有匹配的东西呢?好吧,假设我想每次都打印一个以千克为单位的值,但我的一些值以磅为单位,一些以公斤为单位。我可以将原始数值包装在一个元组中,并使用一个原子来 标记 这些值,这样我就知道它们的意思了。例如,像 {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).
看,我们通常不会在可以帮助的情况下进行显式递归。原因是在简单的情况下我们已经有higher-order functions made to simplify simple iteration over lists. In the case where we want a side effect and don't care about the return value, like printing the weights as above, we use lists:foreach/2
.
回到上面的"change"例子,如果我们已经知道我们要对每个值执行change/1
,但是return原样返回相同的映射,它使更多感觉要么使用 list comprehension or lists:map/2
.
列表理解是映射的一种特殊语法,它也可以包括守卫。将函数映射到列表中的每个值并 returning 该列表的简单情况如下所示:
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}
形式,以及 [=40 形式的任何元组=] 与第二个匹配,但不加改变地传递。我们现在可以简单地将函数映射到一个列表中:
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}.
这里的程序逻辑不需要太多的猎豹翻转。
Erlang 简单得令人厌烦 并且作为一种语言非常微小,一旦大多数过程逻辑的不必要性让你突然陷入其中 "get new eyes"。一些相关的 Q/As 背景信息可能对您的旅程有所帮助:
- Erlang Recursive end loop
- How does the recursive call work in this erlang function?
- Explanation of lists:fold function