Search code examples
erlangnestedrecordlist-comprehensionrecords

In erlang, how do you "list comprehend" the manipulation of deeply nested records?


I found myself in the position of needing to increment a value which was deeply nested in a series of erlang records. My first attempts at doing this with list comprehensions were dismal failures. Originally, the list contained a number of records where the target value would be absent because the record that contained it would, at some level, be undefined.

I dealt with that easily enough by using lists:partition to filter out only those entries that actually needed incrementing, but I was still unable to come up with a list comprehension that would do such a simple operation.

The code sample below probably doesn't compile - it is simply to demonstrate what I was trying to accomplish. I put the "case (blah) of undefined" sections to illustrate my original problem:

-record(l3, {key, value}).
-record(l2, {foo, bar, a_thing_of_type_l3}).
-record(l1, {foo, bar, a_thing_of_type_l2}).

increment_values_recursive([], Acc
increment_values_recursive([L1 | L1s], Acc) ->
    case L1#l1.a_thing_of_type_l2 of
        undefined -> NewRecord = L1;
        L2        ->
            case L2#l2.a_thing_of_type_l3 of
                undefined    -> NewRecord = L2;
                {Key, Value} ->
                    NewRecord = L1#l1{l2 = L2#l2{l3 = {Key, Value + 1}}}
            end
    end,

    increment_values_recursive(L1s, [NewRecord | Acc]).

increment_values(L1s) ->
    lists:reverse(increment_values_recursive(L1s, [])).

........

NewList = increment_values(OldList).

That was what I started with, but I'd be happy to see a list comprehension that would process this when the list didn't have to check for undefined members. Something like this, really:

increment_values_recursive([], Acc
increment_values_recursive([L1 | L1s], Acc) ->
    %I'm VERY SURE that this doesn't actually compile:
    #l1{l2 = #l2{l3 = #l3{_Key, Value} = L3} = L2} = L1, 
    %same here:
    NewRecord = L1#l1{l2=L2#l2{l3=L3#l3{value = Value+1}}},  
    increment_values_recursive(L1s, [NewRecord | Acc]).

increment_values(L1s) ->
    lists:reverse(increment_values_recursive(L1s, [])).

AKA:

typedef struct { int key, value; } l3;
typedef struct { int foo, bar; l3 m_l3 } l2;
typedef struct { int foo, bar; l2 m_l2 } l1;

for (int i=0; i<NUM_IN_LIST; i++)
{
    objs[i].m_l2.m_l3.value++;
}

Solution

  • It is not as hard as it seems. @Peer Stritzinger gave a good answer, but here is my take, with a clean list comprehension:

    -record(l3, {key, value}).
    -record(l2, {foo=foo, bar=bar, al3}).
    -record(l1, {foo=foo, bar=bar, al2}).
    
    increment(#l1{al2 = Al2}=L1) -> L1#l1{al2 = increment(Al2)};
    increment(#l2{al3 = Al3}=L2) -> L2#l2{al3 = increment(Al3)};
    increment(#l3{value = V}=L3) -> L3#l3{value = V + 1}.
    
    test() ->
      List =
        [ #l1{al2=#l2{al3=#l3{key=0, value = 100}}}
        , #l1{al2=#l2{al3=#l3{key=1, value = 200}}}
        , #l1{al2=#l2{al3=#l3{key=2, value = 300}}}
        , #l1{al2=#l2{al3=#l3{key=3, value = 400}}}],
      [increment(L) || L <- List].