Search code examples
processerlangspawn

erlang processes communication (code error)


I am new to Erlang. In my code I try to give each of my 2 processes a list of numbers. The process oid should send all even numbers in its list to the process eid, which should send all odd numbers in its process to oid. When a process finishes filtering its own list it starts reading the messages received from the other process, adds them to the output list, and finally prints that list.

When I run the code I get these errors:

=ERROR REPORT==== 28-Apr-2015::23:01:25 ===
Error in process <0.296.0> with exit value: {if_clause,[{test,odd,2,[{file,"test.erl"},{line,25}]}]}

=ERROR REPORT==== 28-Apr-2015::23:01:25 ===
Error in process <0.297.0> with exit value: {if_clause,[{test,even,2,[{file,"test.erl"},{line,49}]}]}

Here's my code:

-module(test).
-export([start/0]).
-export([odd/2]).
-export([even/2]).

start() -> 
    register(oid, spawn(test, odd, [[1,2,3,4,5],[]])),
    register(eid, spawn(test, even, [[6,7,8,9],[]])).
%---------------------------------------------
odd(c,O) ->
    receive
        done -> 
            lists:foreach(
                fun(X) -> io:fwrite("~p\n", X) end, 
                io:fwrite("~p\n", oid)
            );
        Num -> 
            O++[Num],
            odd(c,O)
    end;
odd([],O) ->
    eid ! done,
    odd(c,O);

odd([A|Rest],O) -> 
    if 
        A rem 2 =:= 0 ->
            eid ! A;
        A rem 2 =/= 0 ->
            O++[A],
            odd([Rest],O)
    end.
%--------------------------------------------
even(c,E)->
    receive
        done -> 
            lists:foreach(
                fun(X) -> io:fwrite("~p\n", X) end, 
                io:fwrite("~p\n", eid)
            );
        Num ->
            E++[Num],
            even(c,E)
    end;

even([],E) ->
    oid ! done,
    even(c,E);

even([A|Rest],E) -> 
    if 
        A rem 2 =/= 0 ->
            oid ! A;
        A rem 2 =:= 0 ->
            E++[A],
            even([Rest],E)
    end.
%---------------------------------------------
pri([H|T]) ->
    io:format("~p~n", [H]),
    pri(T);
pri([]) ->
    true.

Solution

  • There are a number of problems with this code. Did you compile it?

    First, an easy problem: your io:fwrite/2 calls need to have their arguments to be printed passed in a list, not as individual terms, so this:

    io:fwrite("~p\n", oid)
    

    is wrong. It should be:

    io:fwrite("~p\n", [oid])
    

    But there's little point in printing a constant oid like that anyway. You should just get rid of this code:

    lists:foreach(
        fun(X) -> io:fwrite("~p\n", X) end, 
        io:fwrite("~p\n", oid)
    );
    

    (which is broken and won't compile anyway) and use your pri/1 function instead (or pri/2, as shown later).

    Next, you're attempting to add elements to lists as if the lists were mutable. Variables in Erlang are immutable. Instead of this:

    O++[A],
    odd([Rest],O)
    

    you need:

    odd(c,O++[Num])
    

    to create a new list to pass to the next iteration, or better yet:

    odd(c,[Num|O])
    

    which is more efficient than appending because it simply adds a new head to the list. Note though that this builds the list backwards, so we'll need to reverse it later. Fortunately, reversing a list is very inexpensive.

    Next, your if statement error messages are caused by the way you pass Rest recursively. When you have the construct [A|Rest], the Rest variable is already a list. There's no need to pass it as [Rest]; it should just be passed as Rest. Let's say A is 1 and Rest is the list [2,3,4,5]; when you pass it to the next recursive call as [Rest], the [A|Rest] in the new call is equivalent to [[2,3,4,5] | []] and the error occurs because [2,3,4,5] rem 2 is a meaningless operation.

    Another problem with the odd/2 and even/2 functions is that they use if at all, since they can use function clauses instead. The if is not often used in idiomatic Erlang code. Still another problem with those functions is that the clauses that send messages don't make recursive calls to handle any elements remaining in Rest. So instead of this:

    odd([A|Rest],O) -> 
        if 
            A rem 2 =:= 0 ->
                eid ! A;
            A rem 2 =/= 0 ->
                O++[A],
                odd([Rest],O)
        end.
    

    you could instead write this:

    odd([A|Rest],O) when A rem 2 =:= 0 ->
        eid ! A,
        odd(Rest,O);
    odd([A|Rest],O) ->
        odd(Rest,[A|O]).
    

    Note that this avoids the need for two rem tests, since any number not detected by the guard in the first clause as being even is automatically odd and so is handled by the second clause. Note also that instead of O++[A] we're using the prepending form [A|O] to construct the new list.

    One more problem is your artificial way of handling the end of the list by passing the atom c to odd/2 and even/2. A better approach is to make odd/1 and even/1 and leave the c out altogether:

    odd(O) ->
        receive
            done ->
                L = lists:sort(lists:reverse(O)),
                pri(oid,L);
            Num ->
                odd([Num|O])
        end.
    

    This approach uses pri/2 to do the printing, and the list passed to it is reversed here to undo the effects of building it via prepending, and sorted to put it in order. The pri/2 function looks like this:

    pri(Id, [H|T]) ->
        io:format("~p: ~p~n", [Id, H]),
        pri(Id,T);
    pri(_, []) ->
        true.
    

    If you run the whole thing, you get something like this:

    2> test:start().
    eid: 2
    oid: 1
    eid: 4
    oid: 3
    true
    oid: 5
    eid: 6
    oid: 7
    eid: 8
    oid: 9
    

    where the true in the middle is the result of the test:start() call, and the order of the printing from the two processes is indeterminate since they're concurrent.