了解这个Erlang代码是什么?

时间:2017-10-28 19:10:02

标签: erlang erlang-shell

一点都不熟悉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_kprint_weight(Converted_List)find_max_and_min(Converted_List)print_max_and_min(Max_object, Min_object).

根据我的理解,它做了以下事情:

  1. 将对象列表转换为某种格式
  2. 打印转换后的列表
  3. 找到Max和Min,并将其放在Object Max和Min
  4. 打印最大和最小对象
  5. 我对传递[{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]).
    

1 个答案:

答案 0 :(得分:1)

不要担心这段代码有点令人困惑。这有点不合时宜。我们马上解决这个问题......

在样式之前,请查看第一个函数convert_list_to_k/1。它会有选择地将对象从标有l的表单转换为标有k的表单。

如何选择?它是作为参数传递给它的列表的第一个元素的形状上的匹配。如果它在l内收到{Name, {l, Weight}}类型值的值,则选择并运行第一个子句,将{l, Weight}部分转换为{k, Weight}值 - I假设这是&#34; l&#34;因为&#34;磅&#34;和&#34; k&#34;为&#34;千克&#34;。

此函数正在进行深度递归,这通常不适合这种特殊情况,因为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).

请注意,当我们可以提供帮助时,我们通常不会进行明确的递归。原因是在简单的情况下我们已经有higher-order functions made to simplify simple iteration over lists。如果我们想要副作用并且不关心返回值,比如如上所示打印权重,我们会使用lists:foreach/2

回到&#34;改变&#34;上面的例子,如果我们已经知道我们想对每个值执行change/1,但是返回完整的相同地图,那么使用list comprehensionlists:map/2更有意义。< / p>

列表理解是地图上的特殊语法,也可以包括警卫。将函数映射到列表中的每个值并返回该列表的简单情况如下所示:

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}形式,以及{{1}形式的任何元组匹配第二个但传递而不被更改。我们现在可以简单地将该函数映射到列表中:

{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

BUT!也许我们在min / max函数中真正想要的只是操作守卫并避免编写一些复杂的主体逻辑。这是一个操作简单的数字列表的一个,稍微的改变将使它适合你正在处理的数据类型(那些元组):

case X > Y ->
    true  -> do_something();
    false -> something_else()
end,

这里的程序逻辑不需要大量的猎豹翻转。

Erlang是如此简单而且很简单,作为一种语言,一旦大多数程序逻辑的不必要性突然沉入你的眼中,就会睁开新的眼睛&#34;。一些与背景信息相关的问答可能对您的旅程有所帮助: