Keep on wishing there is a better record in Erlang
As nice as the Erlang platform is, there is always something that makes you pull your hair. My most hated feature in Erlang is record. It provides a feature like struct in C, but it sucks really hard.
Working on a very data-intensive application, you always have some complex data structures to deal with. Modelling layers and layers of data with lists, tuples, and proplist is weird, and unintuitive. You start wishing there is a class, allowing you to encapsulate your data structure in a more meaningful way.
The only thing in Erlang that comes as close as you can get to a class is record. But after defining your first record, your hands start to scream. It’s so verbose that you keep on typing, typing and typing.
Here is a record, which is quite simple, really:
-record(address, {street = "", city = "", state, zipcode}).
-record(phone, {areacode=[], number=[]}).
-record(name, {first="", last=""}).
-record(person, {name=#name{}, homephone=#phone{}, workphone=#phone{}, address=#address{}}).
-record(project, {name="", owner=#person{}}).
That’s it, a record defining a person with name, address and phone numbers.
But let’s look what we need to do to create a person record:
Name=#name{first="xp", last="tran"}.
HPhone=#phone{number=[1,2,2,3]}.
WPhone=#phone{number=[3,3,3,3]}.
Address=#address{street="bibo rd", city="shanghai"}.
P = #person{name=Name, homephone=HPhone, workphone=WPhone, address=Address}.
Here, you create a variable Name, and declare it as a record of type name, and fill the different fields with values. Finally, you create a variable P by declaring it a record of type person. Even though Erlang is a dynamically-type language, you really have to declare the type of your record.
So far, it looks ok. But let’s look at how we want to acces it:
print_person_detail(P) when is_record(P, person) ->
io:format("First name: ~s, Last name: ~s ~n",
[(P#person.name)#name.first, (P#person.name)#name.last]),
io:format("Home phone: ~w ~w ~n",
[(P#person.homephone)#phone.areacode, (P#person.homephone)#phone.number]),
io:format("Work phone: ~w ~w ~n",
[(P#person.workphone)#phone.areacode, (P#person.workphone)#phone.number]),
io:format("Address: ~s ~s ~w ~w ~n",
[(P#person.address)#address.street, (P#person.address)#address.city,
(P#person.address)#address.state, (P#person.address)#address.zipcode]).
The code looks hairy, and your hands scream “I don’t want CTS, I don’t want CTS”… Even with code completion, it’s still too much.
Let’s look at what we need to do to access the first name of the person record:
(P#person.name)#name.first
This tells Erlang that we want to access the member first in name, which is of type record name, which itself is a member of P, which is of type person.
That’s right, you have to state the record type each and every place where you reference the member of a record variable.
Remember when you first learned the language, and you were told by everyone that the most powerful feature in Erlang is pattern matching? Yeah, you can use the pattern matching idiom in your function definition to extract record members into local variables, so that you don’t have to type so much verbosity.
As an exercise, you go ahead, rewrite that print_person_detail() function. I warn you, I did, but then, I wanted to poke a fork in my eyes.
Anyway, you go ahead, working happily with records. You load your records into your running application, and you realize that your record is not complete (yet) and you need to do some modification. That’s not a problem, Erlang is dynamic, you can easily upgrade. In a way, that’s true, but Erlang’s dynamic nature does not apply to record. Records are compile-time feature.
At the end, I rewrote almost all my records into proplist. That record feature is not worth the hassle. I wish we will rid it off Erlang some days, and replace it with something better.
Agree, the record thing is bolted on, and is awful to use. And I also don’t like the Erlang syntax, the most annoying thing for me is probably the statement termination symbol, it’s not consistent.