tags:

views:

174

answers:

3

I've figured out the Erlang-style loops: tail-recursion with functions that take all the "variables that don't vary":

%% does something, 80 bytes at a time
loop(Line, File) -> loop(Line, File, 0).
loop(Line, File, Count) -> 
    do_something(Line, Count),
    case file:read(File, 80) of
        {ok, Line2} -> loop(Line2, File, Count + 1);
        eof -> file:close(File);
        {error, Reason} -> {error, Reason}
    end.

But, what is the best way to increment a counter in Erlang? In most programming languages, the way you count things is by incrementing a variable (ie. count += 1;). Erlang's variables don't vary, so we have to be creative. Fortunately, we have options...

We can pass a Counter variable with our functions, and increment it with each function call. We can use the process dictionary to store a count, and get and put to increment it. We can use ETS, the local data storage for processes. We can use a counter process (!!!):

loop(Count) ->                            incr(Counter) ->
    receive                                   Counter ! { incr }.
        { incr } -> 
            loop(Count + 1);              get_count(Counter) ->
        { report, To } ->                     Counter ! { report, self() },
            To ! { count, Count },            receive
            loop(Count)                           { count, Count } -> Count
    end.                                      end.

I'm sure there are other ways too, depending on the scope. What's considered "best practice" for incrementing a variable in Erlang?

+3  A: 

It all depends on what you are using the counter for. Anything global like the number of messages handled by q system should use ets:update_counter. If it is not global I usually just include it in the parameters like you showed.

Lukas
+3  A: 

Don't use the process dictionary.

The 'normal' loop that you are expecting (ie a for loop or a do while) is usually implemented in a recursive function in Erlang so if you are incrementing a 'normal' counter do it in the function calls like you show up top.

Don't use the process dictionary.

In case you missed, can I just point out that you should not use the process dictionary.

Gordon Guthrie
Also, don't use the process dictionary.
Greg Hewgill
Yet strangely the process dictionary is used in almost every application in the Erlang/OTP distribution. Like `inets`. Or `orber`. Or `docbuilder`. Or `ic`. Or `megaco`. Or `tv`. Or `cosNotification`. Or `eunit`. Or `reltool`. Or `compiler`. Or `erts`. Or `test_server`. Or `appmon`. Or `ssh`. Or `debugger`. Or `kernel`. Or `gs`. Or `os_mon`. Or `pman`. Or `stdlib`. Or `percept`. Or `xmerl`. Or `asn1`. Or `mnesia`. Or `common_test`. Or `parsetools`. Or `dialyzer`. Or... It would be easier to believe the "no process dictionary" meme if the community stayed on message.
JUST MY correct OPINION
The general rule is that "if you wonder if you should use the process dictionary, you shouldn't use it" and "you'll know when you need it." To be fair, while there are valid process dictionary uses, most of them don't have to do with 'incrementing variables' but rather 'storing process metadata', as far as I know.
I GIVE TERRIBLE ADVICE
Oh, I agree there I GIVE TERRIBLE ADVICE. I would never even consider using the process dictionary for incrementing variables. I just think the mindless mantra "don't use the process dictionary" is silly given that, you know, the core distribution of Erlang and all its attendant libs makes extensive use of it. Your wording is much better. "If you wonder the answer is no" and "you'll know when you need it" makes far more sense.
JUST MY correct OPINION
A: 

The standard way of incrementing a counter is as in your first example. By adding a variable to the call and incrementing it. I think that you get confused by the lack of for loops and possibility to update values.

Note that:

repeat(Times) when Times >= 0 -> repeat(0, Times).

repeat(Times, Times) -> done;
repeat(N, Times) ->
  do_a_side_effect,
  repeat(N + 1, Times).

compiles to (more or less) the same thing as (in pseudo code):

repeat(Times) ->
  while (N < Times) {
    do_a_side_effect
    N++
  }
  return done

If you want to accumulate the result there are ways to do that as well.

Either use the lists package or accumulate the result yourself:

loop(File) ->
  {ok, Fd} = file:open(File),
  loop(Fd, 0, []).

loop(Fd, Count, Acc) ->
  case file:read(Fd, 80) of
    {ok, Line} ->
       Result = do_something(Line, Count),
       loop(Fd, Count + 1, [Result | Acc]);
    eof ->
      file:close(File),
      {Count, lists:reverse(Acc)};
    {error, Reason} -> {error, Reason}
  end.

Or something similar based on your example.

Edit: returned Count as part of the return value as well, since it seemed to be important.

Daniel Luna