∴ikura

Records in function heads

Erlang records get a bit more backlash than they deserve. I like using
them quite a bit, and in fact it’s Erlang maps that haven’t won me
over yet.

The reason I like them, is they force my modules to adhere to rigid
data models. This has the benefit of making me slow down and think a
bit ahead of time. There is only one small issue I have with them:
records are slightly insane. Records behave a tad differently when
used in function bodies than in function heads.

The Erlang mailing list addresses this in dozens of threads, and
there are blog-posts that have brought it up, too. But I wanted to
address it again, and also share a workaround I use when it comes
to default record values as they play-out in function heads.

Record refresher

To set the stage here, a record can have default values assigned. Not
unlike how a SQL database field can have default values like ‘NULL’
or a time-stamp, so too can an Erlang record.

A record upon construction can either take a value on that field, and
if none given, take the one pre-specified.

Some code

Let’s play with a module that has two records. The ‘animals’ record
will have one default value, and ‘plants’ will have none.

N.B. In order to capture the value for ‘aquatic plants’ — ie. ‘A’
— we have to know that it sits in the second position in the tuple.
This is not cool. Luckily, records save us from this pedantic issue.

To get to the crux of the matter — and the point of this post — it’s
often desirable to explicitly match on default record values in the
function head. Not so obvious at first, is that #animals{} when in a
function head does not mean {animals, undefined, [ducks]}. Rather,
it equates to something like {animals, _Foo, _Bar}.

Now, how could we re-write the first clause of the ‘info’ routine so
it only matches when ‘foul’ equates to the default value of [ducks]?

The only thing unsophisticated about this solution is its verbosity.
But, alas, it gets us to where we need to be: we ensure that the
parameter is an animal record, and enforce a match to the default
field value (as desired).

The heuristic here is we don’t want to hard-code default values in
the guard. We want to let it change dynamically should someone want a
new default value for the ‘foul’ field. By guarding the clause with
the strange #animals{}#animals.foul we accomplish just that.