Record selectors

Matthias Lang matthias@REDACTED
Tue Jan 7 14:46:02 CET 2003


Daniel Dudley writes:

 >     -module(test).
 >     -export([selector/0]).
 > 
 >     -record(person, {name, phone, address}).
 >     -record(country, {code, name}).
 > 
 >     selector() ->
 >        Person = #person{name = "Joe",
 >                         phone = [0,8,2,3,4,3,1,2]},
 >        Country = #country{code = 47,
 >                           name = "Norway"},
 >        print_name(Person).
 > 
 >     print_name(X) ->
 >        io:fwrite("~p~n", [X#country.name]),
 >        io:fwrite("~p~n", [X#person.name]).

[...]

 > The call X#country.name does not produce an error or
 > failure because the name of the country record's second
 > field is 'name'. Consequently, the value of the second
 > field (third element) in the X variable, which is bound to
 > a person record, is returned.

 > This example illustrates that not only is the reference to
 > a specific record-type in the record selector syntax
 > superflous, it is downright "dangerous" because it invites
 > error-prone code.

[...]

 > The bottom line is that a record selector is tied to the
 > type of value stored in the variable being investigated. If
 > this type is (as expected) a tuple, then the actual record
 > type is (must be) determined by reading the first element
 > of the tuple. Only then can one decide which of the
 > remaining elements in the tuple corresponds to the
 > specified field.

A way to do that without having to paste on a guard is to pattern match:

  print_name(#country{name = Name}) ->   
          io:fwrite("~p~n", [Name]).

this causes the above to fail, which is probably what you wanted.

But no matter what you do, you can never be sure that a value really
is what you think it is. Example:

    %% a.erl
    -module(a).
    -export([go/0]).
    -record(person, {name, age}).
    
    go() ->
    	X = b:person(),
    	name(X).
    
    name(X) when record(X, person) -> 
    	X#person.name.

    %% b.erl    		
    -module(b).
    -export([person/0]).
    -record(person, {age, name}).
    person() -> #person{name = "Robert was (ir)responsible", age = 45}.

    %% Erlang shell:
    9> a:go().
    45

Contrived? No. This is a typical 'gotcha' when doing a hot
upgrade. For instance, you might have removed some fields from the
'state' record in a gen_server. You load in the new code and forget to
convert the old state to the new state in code_change/3 and the
gen_server chokes on the next request it gets.

You were talking about Erlang's bottom line. Records are a wart on
Erlang's bottom. Not pretty. Not really that useful. Cause a few
somewhat uncomfortable problems. But they're not going to kill you and
they're surprisingly difficult to remove. Fiddling with them isn't
going to improve things. (milked that metaphor for all it was worth
and then some...)

The only nice thing about records is that they're considerably less
error prone than using bare tuples. In some situations.

See also

   http://www.bluetail.com/wiki/showPage?node=ErlangFlaws

Matthias



More information about the erlang-questions mailing list