[erlang-questions] Selective receive issue

Anders Ramsell anders@REDACTED
Mon Sep 1 09:53:46 CEST 2008


Hi!

Sorry for jumping in to an old thread like this but I believe
there is one way to approach this that hasn't been tried.

Let's start with a slight rewrite of Vlad's example:

  loop(Running) ->
      receive
        start -> loop(true);
        stop  -> loop(false)
      after 0 ->
              if Running -> process_one_step(), loop(Running);
                 true    -> loop(Running)
              end
      end.
 
  process_one_step() ->
      receive
        {input, Data} -> Data
      end.

The problem was that with two separate receive clauses you can't
add an anonymous "catch all" to clean up unwanted messages in
either of them because you might then throw away wanted messages
too. And since we don't know how the unwanted messages look we
can't easily pattern match on them.

But we do know how the unwanted messages *doesn't* look.
Let's add some clauses to loop throwing away messages.

    loop(Running) ->
        receive
            start -> loop(true);
            stop  -> loop(false);
   
            X      when not is_tuple(X) -> loop(Running);
            X      when tuple_size /= 2 -> loop(Running);
            {X, _} when X /= input      -> loop(Running)
        after 0 ->
                if Running -> process_one_step(), loop(Running);
                   true    -> loop(Running)
                end
        end.
   
    process_one_step() ->
        receive
            {input, Data} -> Data
        end.

Now loop should throw away all junk in the inbox. However in the
"real world" our messages may be quite a bit more complex and
doing this could then obscure what we really want to do.   
Lets move the cleaning code to it's own function with a separate
receive.

  loop(Running) ->
      my_flush(),
      receive
          start -> loop(true);
          stop  -> loop(false)
      after 0 ->
              if Running -> process_one_step(), loop(Running);
                 true    -> loop(Running)
              end
      end.
 
  process_one_step() ->
      receive
          {input, Data} -> Data
      end.
 
  my_flush() ->
      receive
          X      when is_atom(X), X /= start, X/= stop -> my_flush();
          X      when not is_tuple(X) -> my_flush();
          X      when tuple_size /= 2 -> my_flush();
          {X, _} when X /= input      -> my_flush()
      after 0 ->
              ok
      end.

This forced us to add another clause or we would have flushed the
start and stop messages too. In return we have a separate
function for flushing unwanted messages which is easier to keep
up-to-date and can be called from more than one place in our
program.
 
As our program grows keeping my_flush correct may be hard unless
we keep wanted messages easy to identify. In my "final" version I
have changed all wanted messages to be 2-tuples starting with a
known tag.

  loop(Running) ->
      my_flush(),
      receive
          {my_tag, start} -> loop(true);
          {my_tag, stop}  -> loop(false)
      after 0 ->
              if Running -> process_one_step(), loop(Running);
                 true    -> loop(Running)
              end
      end.
 
  process_one_step() ->
      receive
          {my_tag, {input, Data}} -> Data
      end.
 
  my_flush() ->
      receive
          X      when not is_tuple(X) -> my_flush();
          X      when tuple_size /= 2 -> my_flush();
          {X, _} when X /= my_tag     -> my_flush()
      after 0 ->
              ok
      end.

If all wanted messages follow a simple principle like this then
my_flush should never have to be updated.

So just because we don't know what the junk looks like does not
have to mean we can't throw it away.

/Anders Ramsell



More information about the erlang-questions mailing list