1

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:

  1. Converts a list of object to some format
  2. Prints the converted list
  3. Find the Max and Min, and place it in Object Max and Min
  4. Prints the Max and Min Object

I am getting confused by the way [{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]).
hemantmetal
  • 107
  • 10
  • 1
    it's just a [tuple](http://erlang.org/doc/reference_manual/data_types.html#id70472) –  Oct 28 '17 at 19:55
  • `convert_list_to_k([{Name, {l, Weight}} | Rest])` is this kind of for loop, which subtract 1 from the `Rest` and may be doing something? – hemantmetal Oct 28 '17 at 20:06
  • 1
    It is Pounds to Kilograms (lbs to kg) conversion. – svujic Oct 28 '17 at 21:25
  • And in the end it's getting(printing) the maximum weight and minimum weight, is that correct? – hemantmetal Oct 28 '17 at 21:27
  • For some reason, I am trying to get it into IDE so I can run the code, but I am not able to do it. Is it possible to do in [tutorialspoint IDE](https://www.tutorialspoint.com/compile_erlang_online.php)? – hemantmetal Oct 28 '17 at 21:30

1 Answers1

1

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:

zxq9
  • 13,020
  • 1
  • 43
  • 60
  • Your tail recursive version doesn't work in constant space because `Accumulator` takes O(N) space and `lists:reverse/1` would take the same amount of space again. Please don't spread [Myth: Tail-Recursive Functions are Much Faster Than Recursive Functions](http://erlang.org/doc/efficiency_guide/myths.html#id61840). – Hynek -Pichi- Vychodil Oct 29 '17 at 07:38
  • @Hynek-Pichi-Vychodil *I mentioned nothing about speed.* Only space. The accumulator accumulates at the same rate the input list is consumed in a typical tail-recursive mapping operation. *This is not true* for a depth-recursive version where *space* is consumed at a *dramatically* higher rate as the call stack grows with partial copies of the input list. I said *nothing* about speed and *also* mentioned that sometimes depth recursion is the appropriate solution. This code was unidiomatic and OP is a beginner who likely doesn't know much about depth-first traversal algorithms. – zxq9 Oct 29 '17 at 10:55
  • @Hynek-Pichi-Vychodil Also, regarding `lists:reverse/1`: This particular function is highly optimized, being an idiomatic call in most Erlang programs, and is implemented in C using pointer arithmetic (it is actually a BIF). Whether it accumulates *any* garbage at all is an implementation detail dependent on the reductions inherent in the executing process prior to execution and not guaranteed to double space in every case. Once again, this is something that should be used *in the typical case* because it supports *idiomatic use of the language*, which is the main point above. – zxq9 Oct 29 '17 at 15:04
  • I must call your information nonsense. Direct recursive or depth-recursive version is not only faster but also consumes less memory! You can see yourself: https://gist.github.com/pichi/d09c8441e9c52a3181bcae7e3b0da30a **Don't speculate! Measure!** So again, Please don't spread [Myth: Tail-Recursive Functions are Much Faster Than Recursive Functions](http://erlang.org/doc/efficiency_guide/myths.html#id61840). – Hynek -Pichi- Vychodil Oct 29 '17 at 16:03
  • The comments are totally foreign to me. But definitely this answer is adding to my knowledge. Thank you very much. – hemantmetal Oct 29 '17 at 16:59
  • @Hynek-Pichi-Vychodil Yes, measure. Measure *during* execution, not after when we don't know what GC has done. Also, total heap is not the only interesting number, particularly when considering the stored stack size. https://gist.github.com/zxq9/c28b2a35c76c4a1e987fc1497a45cc58 The problem becomes more dramatic the more things your function does per iteration (you can see a little of this effect with the string version of the test function, but it is still a canned example). It is interesting to watch both versions evolve as GC runs take place. Anyway, my point was idioms, not performance. – zxq9 Oct 30 '17 at 00:44
  • You don't have clue that stack shares memory with heap so total_heap already contain stack. LOL. So if you look at your deliberately unfair comparison direct recursion took 16Kwords and tail recursive 12Kwords. It is so _dramatically_ higher :-D How long it took you to come with this pathetic attempt? – Hynek -Pichi- Vychodil Oct 30 '17 at 15:03
  • If you are interested in idioms, compare tail recursive version and direct recursive version. Direct version is shorter, faster and easier to understand. If you have to call `lists:reverse/1` to get a tail recursive version, just don't do it. It is nonsense. It makes code harder to read and is not worth it. It is so for last four or five versions of Erlang. Don't spread this myth about tail recursion. If you need `lists:reverse/1` Erlang compiler will do a comparable good job without it. – Hynek -Pichi- Vychodil Oct 30 '17 at 15:11
  • @Hynek-Pichi-Vychodil Clearly the superstar of every team to be graced with your brilliance. – zxq9 Oct 30 '17 at 15:39