5

I need function which returns true, when record has certain field and vice versa. Example:

-record(robot, {name,
            type=industrial,
            hobbies,
            details=[]
            }).

Crusher = #robot{name="Crusher", hobbies=["Crushing people","petting cats"]}.

SomeFunction(Crusher, hobbies). %% returns true
SomeFunction(Crusher, fantasy). %% returns false

Sorry for my English. It was exercise in Functional Programming in my University. It was written for Lisp, where is simple solution. But for me it was required by Erlang. thank you very much for your help. I wrote some ugly function. It is not solution for my first question. It is enough for my teacher.

searchArray(_, []) -> false;
searchArray(X, [X|_]) -> true;
searchArray(X, [_|T]) -> searchArray(X, T).

existField(Field) ->
searchArray(Field, record_info(fields, robot)).

And I think, too, maps is much more usefull here.

  • Records are like a macro, they get transformed to tuples, so you cannot check the existence of a field at runtime (but at compile-time you can check certain things by treating a record as its own type). Records are not "hashes" or "dictionaries" like in Perl/Python/Ruby. Instead you can use a {Key,Value} type and check for a specific key. But I think this is an X-Y problem. What are you trying to *do* in the program? Probably there is a better way to handle your general problem starting at a higher level in the program architecture than whatever led you to this specific detail of technique. – zxq9 Dec 02 '14 at 22:51

3 Answers3

4

Erlang records Demystified

You should understand that a record in erlang is just syntactic sugar that will ultimately become a regular tuple.

I'm sure you've done your homeworks and did have a look at the official reference here.

Let's elaborate on what you found there and take your robot record as an example. If you add the fellowing code somewhere in your module

io:format("My happy robot is ~p", #robot{name="foo", type=some_atom}).

You'll hopefully see something similar to this

My happy robot is {robot, "foo", some_atom, undefined, []}

Notice that the record is a tuple of size NumberOfField + 1. With the record name as the first element (the guard). You'll also notice that there is no reference to the fields that you've declared. This is because the record notation is simply a way to access tuple elements by name, rather than by position.

OK, you've opened an erlang shell, tried the command and it didn't work, that's normal. The shell just don't know about records (newer versions can, but with some magic). If you try the command in a module, it will work just fine. What you should understand, is that once your module is compiled, all the records defined in it are expanded. The resulting compiled module has no references to records anymore, neither attributes nor code.

Robot = #robot{name="foo", type=some_atom},
Robot#robot.type
%% = "foo"
%% Internally, sometime during the parsing, the function
%% erl_expand_records:module/2 will be called and transform the code to
%% something equivalent to this:
Robot = {robot, "foo", some_type, undefined, []},
element(3, Robot).

Of course, what happens in reality is a little bit more complex because erl_expand_records:module/2 works on AbstractForms, but you get the idea.

Now to answer you're question. You have a couple different options and you should pick the one that best suites your needs:

Option 1: Mastering the default value

When you define your record fields, you can give them an optional default value. If you don't specify that value, erlang will use undefined.

Example:

-record(record_1, {foo=bar,baz}).
io:format("~p", #record_1{}).
%% Will print
{record_1, bar, undefined}

Notice how the second field is set to undefined. From here, to solution to your question is quite simple

is_defined(undefined)->false;
is_defined(_)->true.

%% And call it like this
is_defined(Crusher#robot.hobbies).

OK, OK. It looks quite different than what you've asked for. In fact you can skip the function altogether and use the record notation directly in your function and case clause matching patterns.

Option 2: Hacking with macros

You can also define a magic macro that implements the idea defined in option 1 like this:

-define(IS_DEFINED(rec, inst, field), (undefined==inst#rec.field)).
%% and then use it like this
?IS_DEFINED(robot, Crusher, hobbies)

Not exactly a function either, but it looks like one.

sitifensys
  • 2,024
  • 16
  • 29
2

You can use record_info(fields, Record) to get list of record fields. You need to know that records and this function is a little bit tricky. Records are translated to fixed size tuple during compilation and pseudo function record_info is added.

#robot{name="Crusher", hobbies=["Crushing people","petting cats"]}

becomes:

{robot, "Crusher", industrial, ["Crushing people","petting cats"], []}

If you want to share record you should put it in .hrl file and include it in each module you want to be able to construct it using "#record_name{}", extract elements with dot operator by field or use record_info.

Since release 17 we have maps which should work for you as well and won't damage your brain.

Łukasz Ptaszyński
  • 1,639
  • 1
  • 12
  • 11
0

Records are not a special type in erlang, but a simple tuple which first term is the name of the tuple. Everything is solved at compilation, so it does not make sense to test the existence of a field at run time since it should not pass the compilation: I don't know any way to access a record field without explicitly using the field name.

So as Lukasz says, you can use map, the new erlang type coming with the R17.

If you really want to make the test, lists:member(Field,record_info(fields,Record)) will do it.

Pascal
  • 13,977
  • 2
  • 24
  • 32