[erlang-questions] Update with complicated data structure

Richard A. O'Keefe ok@REDACTED
Tue Nov 24 05:34:05 CET 2015


On 23/11/2015, at 11:06 pm, YuanZhiqian <on-your-mark@REDACTED> wrote:

> Hi Guys,
> 
>   Sorry to bother you again... I am totally a green hand in Erlang :(
> 
>   Here is the problem I am now struggling with: in C&C++, we are always able to update a field in an object simply using obj.field1 = Value; But in Erlang, since its once-bound-never-change policy, people would have to find a way around to achieve the same goal,

Be VERY VERY CLEAR what "THE SAME GOAL" really means.

It does *not* mean "fighting the language on the beaches,
on the landing grounds, in the fields and the streets,
(and) in the streets" and "never surrender"ing in your
determination to *FORCE* the low level imperative style
on your functional language.

It means achieving the same APPLICATION-LEVEL GOAL (not
code-level goal) by some possibly very different means.

> and things are getting worse when the data structure is a little more complicated, which is my case:
> 
>   I have a list of records, whose definition is as follows
> 
> -record(company_info, {
>         company_id,
>         budget,
>         consumption,
>         compaign_ids
>     }).
> 
> What I want to do is to add a value to the consumption of the record whose company_id is given, and returning error message if there's no such record found.

We stop right there.
First, you are presuming a structure in which you don't
KNOW whether a company-id is present or not.
Why?
Second, you are using a list, which is a very nice data
structure, but the catalogue of things lists are good at
does not include "searching".  You might find gb_trees:
more apt to your needs.

    case gb_trees:lookup(Company_Id, Companies)
      of none -> raise whatever exception you want
       ; {value,Company_Info=#company_info{budget=Old}} ->
         Updated =
            Company_Info#company_info{budget = Old + Delta},
         gb_trees:update(Company_Id, Updated, Companies)
    end

You should probably break this into two small functions:

% (K, gbtree(K,V), (V -> V)) -> gbtree(K,V).

update(Key, Tree, Fun) ->
    gb_trees:update(Key, Fun(gb_trees:get(Key, Tree)), Tree).

increment_consumption(Info, Delta) ->
    Info#company_info(Info#company_info.consumption + Delta).
         
... update(Company_Id, Companies, fun (Info) ->
        increment_consumption(Info, Delta) end)
...


> Written in C, that would be:
> 
> ///////////////////////////////////In C&C++///////////
> CompanyInfo co_list[1000];
> /* initialized somewhere else ... */
> 
> for (size_t i = 0; i < 1000; ++i) {
>   if (co_list[i].comany_id == co_id) {
>     co_list[i].consumption += price;  
>     break;
>   }
> }
> 
> if (i == 1000)
>   err_msg = "bla...";
> //////////////////////////////////////////////////////

Written in C that would be DISASTROUS if you ever had
1001 companies...  (Where did this practice of using
size_t for indices come from?  That's what 'int' is for!)

(a) You need to break your Erlang code into smaller,
    individually meaningful pieces.

(b) You need to consider AND DOCUMENT why you want to
    use a data structure that takes linear time and
    turns over linear space for an update, when a data
    structure offering logarithmic time and space turnover
    is available.

(c) List comprehensions are generally easier to read than
    calls to lists:map/2.

(d) Avoid thinking in terms of Booleans;
    also, use pattern matching more.
    case [Info || Info = #company_info{company_id = Co_Id} <- Companies]
      of [Found] -> ...
       ; [] -> no match
       ; [_,_|_] -> multiple matches
    end
    Thinking in terms of lists:any made it impossible for you
    to notice duplicate entries; writing
    [Found] = [C || C = #company_info{company_id = Co_Id} <- Companies]
    makes it impossible NOT to notice.

(e) Since the record is a 'company_info' record, what is the
    point of calling the key field 'company_id' rather than
    just 'id'?  You didn't call 'budget' 'company_budget'.



> 
> But in Erlang, I couldn't find a straightforward way to do this, my code is like this:
> 
> ////////////////////////////In Erlang//////////////////////////
> %Co_list is the list of company_info records
> 
> cal_win_notice({Co_id, Ca, Adgrp, Price, Cur}, 
>     #state{company_list = Co_list, campaign_list= Ca_list} = State) ->

(f) Hvy abbrvtn mks ths hrd 2 ndrstnd.
(g) Why do you have a mixed tuple that is not a record
    and is not separate arguments?

>     case lists:any(fun(#company_info{company_id = A}) -> A == Co_id end, 
>             Co_list) of
>         true -> 
>             New_co_list = lists:map(fun(R) -> 
>                         case R#company_info.company_id of
>                             Co_id ->
>                                 R#company_info{consumption = R#company_info.consumption + Price};
>                             _ ->
>                                 R
>                         end
>                     end,
>                 Co_list),
>             {ok, State#state{company_list = New_co_list}};
>         false ->
>             {not_found, State} 
>     end.

No doubt you have a good reason for returning {not_found,State}
instead of crashing (raising an exception), but it's probably
worth documenting it.  I say this because this is hard to
compose: when you call your function you will have to handle
two possible outcomes.  Notice how my code got simpler when
I switched from gb_trees:lookup/2 to gb_trees:get/2.

> Well, as shown above, the codes in bold fonts are doing the same logic which in C can be done in just one expression.

It wasn't one expression in C.
It was 6 statements, including at least one capacity bug.
You have to look at the *whole* thing, not just one tiny
bit of it.

> This will certainly make the code unnecessarily verbose when there are more similar operations to come. What should I do? I think this is due to Erlang's feature, but in case I am wrong and making a simple thing complicated ...

Yes you are,
by insisting that it be done the way you would do it in C.

Make the code out of small composable pieces that are
separately meaningful. 





More information about the erlang-questions mailing list