Search code examples
printingerlangerlang-otpgen-server

Why do I get twice the same message?


I'm quite new in Erlang, and I'm developing a little program to simulate a robot which collect cans:

-export([start/0]).

start()->
    Believes=#{bin_position=>0, num_cans=>1, can_position=>4, my_position=>1},
    PID=spawn(fun()-> believes(Believes) end),
    action(PID,Believes).

believes(NewBelieves)->

    receive
        {add,Key,Value}->
            N1=maps:put(Key,Value,NewBelieves),
            print(N1),
            action(self(),N1),
            believes(N1);
        {remove, Key}->
            N2=maps:remove(Key, NewBelieves),
            print(N2),
            action(self(),N2),
            believes(N2);
        {update, Key, Value}->
            N3=NewBelieves#{Key => Value},
            print(N3),
            action(self(),N3),
            believes(N3);
        {get}->
            action(self(),NewBelieves),
            believes(NewBelieves)
    end.

print(Map)->
    List=maps:to_list(Map),
    io:format("Believes: ~p~n",[List]).

action(PID, Map)->
    case {is_holding(Map),is_over_bin(PID,Map)} of
            {false,false}->
                %io:format("Is not holding and is not over the bin ~n"),
                %timer:sleep(1000),
                case is_over_can(PID,Map) of
                    false->move(PID,right,Map);
                          % timer:sleep(1000);
                    _->hold(PID)
                       %timer:sleep(1000)
               end;
            {_,false}->
                %io:format("Is holding but is not over the bin ~n"),
                %timer:sleep(1000),
                move(PID, left, Map);
                %timer:sleep(1000);
            {_,_}->
                %io:format("Is holding and is over the bin ~n"),
                %timer:sleep(1000),
                drop(PID),
                timer:sleep(10000),
                exit(self(),kill)
        end.

is_over_can(PID, Map)->
    case same_position(maps:find(my_position,Map),maps:find(can_position, Map)) of
        equal->hold(PID);
        _->false
    end.

is_over_bin(PID, Map)->
    case same_position(maps:find(my_position,Map),maps:find(bin_position, Map)) of
        equal->drop(PID);
        _->false
    end.

is_holding(Map)->
    maps:is_key(holding,Map).

%is_gripper_on(Map)->
%   map:is_key(gripper_on,Map).
%is_touching(Map)->
%   map:is_key(touching,Map).

same_position({ok,A},{ok,B}) when A=:=B -> equal;
same_position(_, _) -> not_equal.

move(PID, Dir, Map)->
    {ok,MyPosition}=maps:find(my_position,Map),
    case Dir of
        right->PID ! {update, my_position, MyPosition+1};
        left->PID ! {update, my_position, MyPosition-1}
    end.  

hold(PID)->
    PID ! {add, holding, []},
    PID ! {update, can_position, nil}.

drop(PID)->
    PID ! {remove, holding},
    PID ! {update, num_cans, 0},
    PID ! {add, cans_collected, 1},
    timer:sleep(10000),
    io:format("Todas las latas han sido recogidas.~n"),
    timer:sleep(10000),
    exit(PID,kill).

And when I run the code, I get the print twice in some cases (and I don't get the last 3 prints, from the drop function):

prueba2:start().

Believes: [{bin_position,0},{can_position,4},{my_position,2},{num_cans,1}]

Believes: [{bin_position,0},{can_position,4},{my_position,3},{num_cans,1}]

{update,my_position,2}

Believes: [{bin_position,0},{can_position,4},{my_position,4},{num_cans,1}]

(Prueba@Usuario)2> Believes: [{bin_position,0}, {can_position,4}, {holding,[]}, {my_position,4}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,4}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,4}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,4}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,3}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,3}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,3}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,3}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,2}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,2}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,2}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,2}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,1}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,1}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,1}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,1}, {num_cans,1}]

Believes: [{bin_position,0}, {can_position,nil}, {holding,[]}, {my_position,0}, {num_cans,1}]

Todas las latas han sido recogidas.

Am I doing something wrong? Is it related with the time that each process need to execute it? I also don't know if there is a way to implement it with OTP, I mean, using gen_server or something.

Thank you so much!


Solution

  • Try running this code:

    -module(so1).
    -export([start/0]).
    
    start()->
        Believes = #{bin_position=>0, num_cans=>1, can_position=>4, my_position=>1},
        PID = spawn(fun()-> believes(Believes) end),
        action(PID,Believes).
    
    believes(NewBelieves)->
    
        receive
            {add,Key,Value}->
                N1=maps:put(Key,Value,NewBelieves),
                io:format("add clause:~n"),
                print(N1),
                action(self(),N1),
                believes(N1);
            {remove, Key}->
                N2=maps:remove(Key, NewBelieves),
                io:format("remove clause:~n"),
                print(N2),
                action(self(),N2),
                believes(N2);
            {update, Key, Value}->
                N3=NewBelieves#{Key => Value},
                io:format("update clause:~n"),
                print(N3),
                action(self(),N3),
                believes(N3);
            {get}->
                action(self(),NewBelieves),
                believes(NewBelieves)
        end.
    
    print(Map)->
        List=maps:to_list(Map),
        io:format("Believes: ~p~n",[List]).
    
    action(PID, Map)->
        case {is_holding(Map),is_over_bin(PID,Map)} of
                {false,false}->
                    %io:format("Is not holding and is not over the bin ~n"),
                    %timer:sleep(1000),
                    case is_over_can(PID,Map) of
                        false->
                            io:format("action() is calling move()~n"),
                            move(PID,right,Map);
                              % timer:sleep(1000);
                        _->hold(PID)
                           %timer:sleep(1000)
                   end;
                {_,false}->
                    %io:format("Is holding but is not over the bin ~n"),
                    %timer:sleep(1000),
                    move(PID, left, Map);
                    %timer:sleep(1000);
                {_,_}->
                    %io:format("Is holding and is over the bin ~n"),
                    %timer:sleep(1000),
                    drop(PID),
                    timer:sleep(10000),
                    exit(self(),kill)
            end.
    
    is_over_can(PID, Map)->
        case same_position(maps:find(my_position,Map),maps:find(can_position, Map)) of
            equal->hold(PID);
            _->false
        end.
    
    is_over_bin(PID, Map)->
        case same_position(maps:find(my_position,Map),maps:find(bin_position, Map)) of
            equal->drop(PID);
            _->false
        end.
    
    is_holding(Map)->
        maps:is_key(holding,Map).
    
    %is_gripper_on(Map)->
    %   map:is_key(gripper_on,Map).
    %is_touching(Map)->
    %   map:is_key(touching,Map).
    
    same_position({ok,A},{ok,B}) when A=:=B -> equal;
    same_position(_, _) -> not_equal.
    
    move(PID, Dir, Map)->
        {ok,MyPosition}=maps:find(my_position,Map),
        io:format("move() is going to send update message~n"),
        case Dir of
            right-> PID ! {update, my_position, MyPosition+1};
            left -> PID ! {update, my_position, MyPosition-1}
        end.  
    
    hold(PID)->
        io:format("hold() is sending add, update messages,~n"),
        io:format("which will result in two prints:~n"),
        PID ! {add, holding, []},
        PID ! {update, can_position, nil}.
    
    drop(PID)->
        io:format("drop() is sending remove, update, add messages,~n"),
        io:format("which will result in three prints:~n"),
        PID ! {remove, holding},
        PID ! {update, num_cans, 0},
        PID ! {add, cans_collected, 1},
        timer:sleep(10000),
        io:format("Todas las latas han sido recogidas.~n"),
        timer:sleep(10000),
        exit(PID,kill).
    

    The output will show that your code is a convoluted mess. Also, when you create more than one process the output can be interleaved and come in a different order than written.

    I'm quite new in Erlang,

    You can only code programs that are as long as your debugging skills can handle. Stick to 20 lines or less for now. You also might want to try using the erlang debugger to step through your code.

    I also don't know if there is a way to implement it with OTP, I mean, using gen_server or something.

    Any infinite loop with a receive clause and a loop variable that holds state can be considered a server, so believes() could be implemented as a gen_server.