[erlang-questions] Style question: accessing record fields in a function clause pattern match?

Jay Nelson jay@REDACTED
Tue Jun 17 18:15:19 CEST 2014

I agree with the responses that in general pattern-matching in the function head
is preferred. There are two caveats, one which you mention and another one that
is deeper and related to data abstraction.

To differentiate between pattern-match and free variable binding, this would be
a perfectly acceptable style:

handle_foo( #state{type=bar} = State) ->
   #state{id=Id, parent=Parent} = State,
   … ;
handle_foo( #state{type=baz} = State) ->
   #state{sibling=Sibling, parent=Parent} = State,
   … .

At the cost of a little redundancy, you make a distinction in the function head of
things that are filtering the patterns, and as a side-effect keep a handle on the
full record as State, then you set up some new local variables for the fields you
are going to compute with. When there are several fields in a record you are
accessing, this separation makes for cleaner semantics which are easier to read.

The key to the record-matching style, is to get a function head pattern-match
crash if the wrong type of data is supplied to the function call. That is desired.
If you don’t match on the #state{} construct, but later access State#state.field
you will get a very different kind of crash and I prefer knowing about bad datatypes
at the entry point to a function call. The above approach retains that feature
and clarifies which elements matter to the function case. As Loic mentioned,
you would also want to include any fields which are best filtered using a guard
on the function head.

The second issue relates to the tradition of data abstraction. If you use a
#state{} record as an implementation and start pattern-matching in code,
then everywhere you access the data structure the implementation is exposed.
You cannot change the implementation without changing all those code locations.
You also cannot easily enforce dependent default values or correlated dependencies
among field values without replicating the logic throughout your code.

The case of gen_server, et al, localizes the code references to a single
module and that makes things clear within the module and opaque outside
the module. But if you pass the record to other modules and do pattern-matching
you will run across two issues eventually:

1) records are compile-time constructs, communicating to other nodes can
get out of sync if compiled on different versions causing bad, bad problems

2) you will be reluctant to change the implementation of the data model
when a better approach is available because it will affect too much code


More information about the erlang-questions mailing list