erlang records trouble
Asked Answered
B

4

6

I'am struggling with records in one of my modules.

I defined on top of my code a record as:

-record(user,  {pid,
                name,
                nick}).

in few words each user is going to be represented as process with its own pid and other fields.

Later on in the module I am doing the following:

Pid = UserPid,
GetUser = fun(X) ->
                if X#user.pid =:= Pid -> true; 
                   X#user.pid=/= Pid -> false 
                end 
      end,
User = lists:filter(GetUser, Users),
io:format("User pid is ~p~n",[User#user.pid]).

Running this code I get:

** exception error: {badrecord,user}

But if I do:

io:format("User ~p~n",[User]).       

It prints

User [{user,<0.33.0>,name1,nick1}]

Can anyone point out what i am missing?

Thanks

Bonnie answered 21/7, 2011 at 8:41 Comment(0)
I
7

Emil's answer about the lists:filter function is correct.

This is how I would rewrite your code, though:

-module(com).

-record(user,  {pid,
                name,
                nick}).

-export([lookup/1]).

lookup(Pid) ->
    Users = users(),
    FilteredUsers = [User || #user{pid = P} = User <- Users, Pid =:= P],
    lists:foreach(fun display/1, FilteredUsers).

display(User) ->
    io:format("User name  is ~p~n",[User#user.name]).   

users() ->
    User1 = #user{pid = 1, name = "Bob", nick = "bob"},
    User2 = #user{pid = 2, name = "Alice", nick = "alice"},
    User3 = #user{pid = 1, name = "Charlie", nick = "charlie"},
    [User1, User2, User3].

I'm assuming you can have multiple pids. If you don't, you can save yourself the foreach.

I believe that using list comprehensions in this case makes the code much more readable. Also, the following:

Pid = UserPid,

doesn't look very useful to me...

Indigence answered 21/7, 2011 at 10:36 Comment(2)
I'd do [U || U <- Users, U#user.pid =:= P].Biogen
thanks a lot to everybody! In fact your examples is perfect for my purposes...about the pid, i don't actually use Pid = UserPid, it was there just to clarify what i was trying to achieveBonnie
S
11

The problems is that lists:filter returns another list, not a single element. So you are basically trying to treat a list as a record. If you look carefully in the output of

io:format("User ~p~n",[User])
%% User [{user,<0.33.0>,name1,nick1}]

you will notice that the statement is wrapped in []. It's a list. If you need just the first user use

[First | Rest] = lists:filter(GetUser, Users)

If you only want the pids use lists:map:

UsersWithPid = lists:filter(GetUser, Users),
Pids = lists:map(fun(U) -> U#user.pid end, UsersWithPid).

Now Pids is list with the pids of the users with pid.

Shelton answered 21/7, 2011 at 8:50 Comment(1)
Note that [First, Rest] will match against a list with exactly two elements. You probably want: [First|Rest]. Or, if you're expecting exactly one result: [First].Indigence
I
7

Emil's answer about the lists:filter function is correct.

This is how I would rewrite your code, though:

-module(com).

-record(user,  {pid,
                name,
                nick}).

-export([lookup/1]).

lookup(Pid) ->
    Users = users(),
    FilteredUsers = [User || #user{pid = P} = User <- Users, Pid =:= P],
    lists:foreach(fun display/1, FilteredUsers).

display(User) ->
    io:format("User name  is ~p~n",[User#user.name]).   

users() ->
    User1 = #user{pid = 1, name = "Bob", nick = "bob"},
    User2 = #user{pid = 2, name = "Alice", nick = "alice"},
    User3 = #user{pid = 1, name = "Charlie", nick = "charlie"},
    [User1, User2, User3].

I'm assuming you can have multiple pids. If you don't, you can save yourself the foreach.

I believe that using list comprehensions in this case makes the code much more readable. Also, the following:

Pid = UserPid,

doesn't look very useful to me...

Indigence answered 21/7, 2011 at 10:36 Comment(2)
I'd do [U || U <- Users, U#user.pid =:= P].Biogen
thanks a lot to everybody! In fact your examples is perfect for my purposes...about the pid, i don't actually use Pid = UserPid, it was there just to clarify what i was trying to achieveBonnie
B
4

As others have pointed out, lists:filter/2 returns a list, even if it's just a single element. The function you're looking for is lists:keyfind/3 (in Erlang R14B03, for R13B04 and earlier, use lists:keysearch/3):

Eshell V5.8.4  (abort with ^G)
1> rd(user, {pid, name, nick}).
user

2> Users = [#user{pid = spawn(fun() -> ok end), name = name1, nick = nick1},
2>          #user{pid = spawn(fun() -> ok end), name = name2, nick = nick2},
2>          #user{pid = spawn(fun() -> ok end), name = name3, nick = nick3}].
[#user{pid = <0.34.0>,name = name1,nick = nick1},
 #user{pid = <0.35.0>,name = name2,nick = nick2},
 #user{pid = <0.36.0>,name = name3,nick = nick3}]

3> lists:keysearch(pid(0,35,0), #user.pid, Users).
{value,#user{pid = <0.35.0>,name = name2,nick = nick2}}

4> lists:keyfind(pid(0,35,0), #user.pid, Users).
#user{pid = <0.35.0>,name = name2,nick = nick2}

5> lists:keyfind(pid(0,99,0), #user.pid, Users).
false

lists:keyfind/3 is preferred because it's simpler.

Using only #user.pid returns the position of the field pid in the #user record:

6> #user.pid.
2
Biogen answered 21/7, 2011 at 11:53 Comment(1)
+1 for #user.pid returns position of field pid in #user record.Joaniejoann
M
0

the variable you are trying to display is not a record but rather, a List with one element inside it. The element inside this list is the record you want. Consider pattern matching the result of your lists:filter in case statement like this:

case lists:filter(GetUser, Users) of
   [] -> 
       %% user not found against that criteria.....
   [User] when is_record(User,user) -> 
     %% user found. Do things here........
   List -> %% are many things matching your criteria ?
end,
...

Also, remember the true clause of the if statement.

Manganin answered 21/7, 2011 at 10:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.