Understanding what this Erlang Code is?

Not at all familiar with Erlang, but am trying to interpret what this code does?

Below is my understanding about the code. Any help will be useful. I am looking at the tutorials but the passing values are confusing in this case.

example- convert_list_to_k([{Name, {l, Weight}} | Rest]) //{1,Weight} <- This one

And how is the value returned in convert_list_to_k ?

let's say for this function block

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([]) ->
    [].

Below is the code with explanations.

-module(erlang_program).
-export([format_weight/1]).

in the above export the /1 represents it's going to receive an attribute(I don't know which attribute)

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).

Kind of main function, which will import convert_list_to_k , print_weight(Converted_List) , find_max_and_min(Converted_List) and print_max_and_min(Max_object, Min_object).

According to my understanding it's doing the following things:

  • Converts a list of object to some format
  • Prints the converted list
  • Find the Max and Min, and place it in Object Max and Min
  • Prints the Max and Min Object

  • I am getting confused by the way [{Name, {l, Weight}} | Rest] [{Name, {l, Weight}} | Rest] is passed

    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]).
    

    Don't worry that this code is a bit confusing. It is somewhat unidiomatic. We'll address that in a moment...

    Before style, look at this first function, convert_list_to_k/1 . It is selectively converting objects from a form marked with l to a form marked with k .

    How is it selecting? It is matching on the shape and value of the first element of the list passed to it as an argument. If it receives a value with an l type value inside like {Name, {l, Weight}} then the first clause is selected and run, which converts the {l, Weight} part to a {k, Weight} value -- I assume here this is "l" for "pounds" and "k" for "kilograms".

    This function is doing depth recursion which is not usually a good fit for this particular case, because Erlang (and most functional languages) have an optimization for tail recursion.

    foo([Thing | Things]) ->
        NewThing = change(Thing),
        [NewThing | foo(Things)];
    foo([]) ->
        [].
    

    This is basically what the function is doing. This means that for whatever size the list is, a new layer of the call stack has to be added because the original list in the first clause cannot be returned without remembering every intermediate value. This will not work on arbitrarily long lists without significant memory overhead and is generally not how things work.

    Imagine in memory seeing this:

    foo([change(Thing1) | foo([change(Thing2) | foo([change(Thing3) | ...]]])
    

    Not very tidy. Sometimes it is the right thing to do, but not in the general case of iterating over a list.

    A tail recursive version would look like this:

    foo(Things) ->
        foo(Things, []).
    
    foo([Thing | Things], Accumulator) ->
        NewThing = change(Thing),
        foo(Things, [NewThing | Accumulator]);
    foo([], Accumulator) ->
        lists:reverse(Accumulator).
    

    This version runs in constant space and is the more idiomatic form of explicit recursion.

    So what about all that matching stuff? Well, let's say I wanted to print a value in kilograms every time, but some of my values are in pounds and some are in kilos. I could wrap the raw number values in a tuple and use an atom to tag the values so I know what they mean. For example, a tuple like {pounds, X} would mean I have a number, X, and it is in pounds, or a tuple {kilos, X} which would mean X is kilos. Both are still weight.

    So how would my function look?

    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]).
    

    So this function works fine as long as it is passed either kind of tuple.

    How about a list of these? We could do explicit recursion like above:

    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.
    

    So this handles a list of values like above. But we don't really need to write all that, do we? We already had a function called print_weight/1 , and it already knows how to do the matching. What we could do instead is more simply define print_weights/1 as a function that uses a list operation:

    print_weights(List) ->
        lists:foreach(fun print_weight/1, List).
    

    See, we usually don't do explicit recursion when we can help it. The reason is that in the simple case we already have 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 .

    Going back to the "change" example above, if we already know that we want to perform change/1 on each value, but return the same map back intact, it makes more sense to either use a list comprehension or lists:map/2 .

    A list comprehension is a special syntax over a map, which can also include guards. The simple case of mapping a function over every value in a list and returning that list looks like this:

    ChangedThings = [change(Thing) || Thing <- Things]
    

    A map looks almost exactly the way lists:foreach/2 did above:

    ChangedThings = lists:map(fun change/1, Things)
    

    Now, going back to your original example... maybe we want to ensure a specific value type. So we could write a simple function that does only that:

    ensure_metric({Name, {l, Pounds}}) ->
        Kilos = Pounds / 0.45359237,
        {Name, {k, Kilos}};
    ensure_metric(Value = {_, {k, _}}) ->
        Value.
    

    That's all we need. What is happening above is that any tuple of the form {Foo, {l, Bar}} matches the first clause and gets converted by the operation in that clause and then repacked to a {Foo, {k, Baz} form, and any tuple of the form {Foo, {k, Bar}} matches the second but is passed along without being changed. We can now simply map that function over a list:

    convert_list_to_k(List) ->
        lists:map(fun ensure_metric/1, List).
    

    Much easier to reason about just one function at a time!

    The min/max function is a bit insane. We would not want to write an if unless we had a fully bounded mathematical case. For example:

    if
        X >   Y -> option1();
        X =:= Y -> option2();
        X ==  Y -> option3();
        X <   Y -> option4()
    end,
    

    This is four tests in a single clause. Occasionally using an if makes sense for that. More often, though, you wind up with what you had above, where a simple comparison happens. In that case a case is much more expressive:

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

    BUT! Maybe what we really want in a min/max function is to just operate over guards and avoid writing some complex body logic. Here is one that operates over a simple list of numbers, a slight change would make it fit the data type you are dealing with (those tuples):

    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}.
    

    Not a whole lot of cheetah flips are needed in procedural logic here.

    Erlang is so boringly simple and tiny as a language that once the needlessness of most procedural logic sinks in you just suddenly "get new eyes". A few related Q/As with background information may be helpful on your journey:

  • Erlang Recursive end loop
  • How does the recursive call work in this erlang function?
  • Explanation of lists:fold function
  • Function chaining in Erlang
  • 链接地址: http://www.djcxy.com/p/66296.html

    上一篇: 如何防止重播攻击

    下一篇: 了解这个Erlang代码是什么?