[erlang-questions] If you are homesick for object.selector

ok <>
Sat Jan 26 05:04:28 CET 2013


> Manipulating nested records is a pain - how about this?
>
> Assume we have three records
>
> -record(top, {a::#one{},     b::#two{},  c::#three{}  }).
> -record(one, {x::#foo{},  y::#bar{} })
> -record(foo, {p,q,r}).
> and I want to unpack the p field from a variable X containing a top
> record.

Disagreeing with Joe Armstrong in the Erlang mailing list is
rather like arguing with God, but if Honi the circle-drawer
(http://www.youtube.com/watch?v=hRfV7XrGfBo) got away with it,
maybe I'll survive.

Why should I assume that?  What does a real example look like?
>
> I don't want to write
>
>   #top{a=#one{x=#foo{p=P}}} = X

I don't mind writing that.  Once.
In one place.
And naming it.

Ideally, I'd write

#american_express(<{a ~ <{ x ~ <{ p ~ X }>}>}> -> X.

but even

american_express(AXP) ->
    AXP#top.a#one.x#foo.p.

will do.

There's a style guideline I've been feeling my way
towards over the last couple of years, and this
thread is helping me to clarify it, but I'm not
quite there yet.  It's something like

"no multi-clause construct should cross more than
one abstraction layer."

I don't count a #state record that merely aggregates
a bunch of state variables as an abstraction layer;
I do count a record that might escape from the boundaries
of its module as an abstraction layer.

So I don't _want_ to write AXP.a.x.p because that code
frightens me with its fragility.  The only x.a.b or
x->a->b I could find in a quick find+grep over my own
C code on this laptop were two lines in some code to
handle doubly linked lists.

If I don't do this sort of thing in C,
why would I be doing it in Erlang?

Just for the sake of pointing out that there _are_
alternatives, let me suggest another approach that
could be taken with records.

Suppose we spotted that .a.x.p was used often enough
to be worth worrying about.  We might decide that it
was worth storing redundantly.  After all, memory
references are _expensive_ on modern machines.

So let's introduce the notion of a *virtual* field.
A virtual field looks just like an ordinary field,
except that you cannot update it, and it is computed,
not fetched.  We'll use

    Field_Name -> Expression

as the syntax, where Expression is a guard expression
so that we can still use records in patterns, and
may include THIS.

Now we define

-record(top, {
    a :: #one{},
    b :: #two{},
    c :: #three{},
    axp -> THIS#top.a#one.x#foo.p
 }).
-record(one, {
    x :: #foo{},
    y :: #bar{}
  }).
-record(foo, {
    p,
    q,
    r
  }).

and use

    #top(axp = P) = X

or
    P = X#top.axp

This code follows the Law of Demeter.  After the definitions,
the source code no longer assumes very much about #top and
*nothing* about #one or #foo, not even that they exist.
All it assumes about #top is that a #top record knows how to
find .axp *somehow* (and safely).

Once the code has been rewritten to use the virtual field,
we can change the definition so that the field is stored
concretely.

(Aside.)  Given that I don't like records very much, and
invented abstract patterns and frames so that we could get
rid of them, I'm not that thrilled that I've just made
records do something frames can't do at all.  Oh well, this
is just an example, not an EEP.  (End aside.)

(Second aside.)  Having warned you that this is just an
alternative to prove that alternatives are possible, this
_is_ something we could practically do, and it _could_
shorten code, and it _could_ simplify a transition from one
representation to another.  Maybe it does deserve serious thought.
(End second aside.)






More information about the erlang-questions mailing list