Arkaitz Jimenez
Arkaitz Jimenez

Reputation: 23178

Erlang pattern matching of a list of records on receive

I'm trying to create a generic receive for a part of my app that requires to wait for several updates at different times.
This is the prototype that it is not working.

receive_info([])->[];
receive_info([RequiredInfo|RestRequiredInfos]) ->
  receive
    RequiredInfo -> [ RequiredInfo | receie_info(RestRequiredInfos)];
  end.

And it is getting called as

[UserInfo, ContextInfo] = receive_info([#userinfo{},#contextinfo{}]),

So, what I'm trying is to send a list of record types it should match and I expect a list of records received.
Not sure if this is doable, because the records set their fields to undefined and the pattern matching does not work, for example:

Trying to receive {user_info,undefined}
Other clause: instead {user_info,12} received

EDIT:
As is_record version supplied by @Adam Lindberg was not working for me I followed a bit that same way of doing things and I ended up with:

receive_infos([]) -> [];
receive_infos([Rec|Records]) ->
receive
    %% Guard expression that compares first element in record with supplied record tag
    Record when element(1, Record) =:= Rec -> 
        [ Record | receive_infos(Records)]
end.

Upvotes: 0

Views: 1562

Answers (3)

Dan
Dan

Reputation: 11069

I think you're on the right track with that last answer, but you can add one more check to make sure you're getting records of the correct size:

receive_infos([]) -> [];
receive_infos([Rec|Records]) ->
receive
    %% Guard expression that compares first element in record with supplied record tag
    Record when element(1, Record) =:= element(1, Rec), size(Record) =:= size(Rec) -> 
        [ Record | receive_infos(Records)]
end.

Since the size and the first element atom are about all you get with records, that's probably about as good as you're gonna do.

Or arguably more straightforwardly, accept those two as arguments:

receive_infos([]) -> [];
receive_infos([{Name, Size}|Records]) ->
receive
    %% Guard expression that compares first element in record with supplied record tag
    Record when element(1, Record) =:= Name, size(Record) =:= Size -> 
        [ Record | receive_infos(Records)]
end.

And call it as

receive_infos([{foo, record_info(size, foo)} | etc. ])

Upvotes: 2

Adam Lindberg
Adam Lindberg

Reputation: 16577

If you rewrite it to:

receive_info([]) -> [];
receive_info([Rec|Records]) ->
    receive
        Msg when is_record(Msg, Rec) ->
            [Msg|receive_info(Records)];
    end.

and call it as:

receive_info([userinfo, contextinfo])

it will do what you want.

Upvotes: 3

D.Nibon
D.Nibon

Reputation: 2919

Several things. You're trying to match RequiredInfo (default record data) with RequiredInfo (filled record data sent as message). You therefor only expect to get data you already have which I assume might not be what you want. You also have some spelling error on receie_info.

If I wanted to fill a list of default records with received populated records and all records must be received or everything fail, I would probably do something like this.

fill_records(List)   -> fill_records(List,length(List)).
fill_records(List,0) -> List;
fill_records(List,N) ->
    receive Rec -> fill_records(lists:keyreplace(element(1,Rec),1,List,Rec),N-1)
    after 5000 -> {error,timeout} end.

You should secure this by protecting against receiving several instances of the same record type or unwanted record types, depending on the surrounding system.

You could of course also just create a receive loop where you only define the number of messages to expect, or maybe look at gen_fsm or gen_server and their internal states and message handling.

Upvotes: 1

Related Questions