Search code examples
erlangtimeout

Understanding selective receives in Erlang


I am reading LYAE chapter on timeouts and i can't wrap my head about what happens in the following scenario:

important() ->
   receive
     {Priority, Message} when Priority > 10 ->
         [Message | important()]
   after 0 ->
     normal()
   end.

normal() ->
   receive
     {_, Message} ->
         [Message | normal()]
   after 0 ->
         []
    end.

1> c(multiproc).
{ok,multiproc}
2> self() ! {15, high}, self() ! {7, low}, self() ! {1, low}, self() ! {17, high}.      
{17,high}
3> multiproc:important().
[high,high,low,low]

I do not understand the following:

  1. Saying after 0 its like using , from what i understand, it happens EVEN if the message matches in the receive clause?
  2. After reading the first message we have [15, important()], which at the second iteration will call normal so we have one one hand [15, important() --3-rd call] and [7,normal() -fourth call]. So how in the end somehow we end up with two lists that get concatenated.
  3. After reading the first 2 messages we have :

important with [15]
normal(first call) with [7]

Now normal() (first call) is already waiting for a new message ,and important now will make a second call to normal() so now in the second call won't we have [1] ?

I do not understand how do the [7] and [1] get merged since they are from separate calls to normal().

I understand for important() since the result gets placed at the end of the list [Message,important()].But that is not the case for normal() since it gets called by important and everytime it should create a new list.

enter image description here

P.S I have added a picture , to further explain my dilemma.I think i understand now that at the end the 2 branches will return their result into [7,15] but i still do not understand in what order.


Solution

  • In a receive, we'll end up either in one of the matching clauses or in the after clause. That is, the after clause will only be evaluated if there is no matching message within the timeout. This is different from how after behaves in a try/catch, where it will be evaluated unconditionally.


    These two functions follow a common Erlang pattern: building a list through recursive calls - the selective receive doesn't really matter here. Note that this expression:

    [Message | important()]
    

    is equivalent to:

    [Message] ++ important()
    

    That is, we're creating a list whose first element is the message we received, and whose tail consists of whatever the recursive call returns. The result is that we get a list of messages received, one after another. The fact that at some point we switch to calling normal() instead of important() doesn't change that - we're still building the list one element at a time.

    Step by step:

    1. We call multiproc:important() from the shell
    2. important receives {15, high}, we're now evaluating [high] ++ important()
    3. The recursive call to important ignores {7, low} because it doesn't match, but receives {17, high} which does. We're now evaluating [high] ++ [high] ++ important()
    4. The second recursive call to important doesn't see any matching message and goes into the after clause. We're now evaluating [high] ++ [high] ++ normal()
    5. The call to normal receives {7, low}, we're now evaluating [high] ++ [high] ++ [low] ++ normal()
    6. The recursive call to normal receives {1, low}, we're now evaluating [high] ++ [high] ++ [low] ++ [low] ++ normal()
    7. The second recursive call to normal doesn't see any matching message and returns [] without making a recursive call. We now have [high] ++ [high] ++ [low] ++ [low] ++ [], which is equal to [high, high, low, low].