在erlang中,你如何“列出理解”深层嵌套记录的操作?

时间:2011-08-19 22:00:57

标签: erlang nested record list-comprehension records

我发现自己处于需要增加深度嵌套在一系列erlang记录中的值的位置。我对列表推导这样做的第一次尝试是令人沮丧的失败。最初,该列表包含许多记录,其中目标值将不存在,因为包含它的记录在某种程度上是未定义的。

我通过使用list来解决这个问题:分区只过滤掉那些实际需要递增的条目,但是我仍然无法提出一个能够进行这么简单操作的列表理解。

下面的代码示例可能无法编译 - 它只是为了演示我想要完成的任务。我把“未定义的案例(等等)”部分用来说明我原来的问题:

-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).

这就是我的开始,但我很高兴看到一个列表理解,当列表不必检查未定义的成员时,它将处理这个。这样的事情,真的:

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++;
}

4 个答案:

答案 0 :(得分:3)

您可以使用列表推导,甚至不需要过滤掉没有嵌套的记录。

为避免可读性问题,我缩短了您的记录定义。

-record(l3, {key, value}).
-record(l2, {foo, bar, al3}).
-record(l1, {foo, bar, al2}).

定义辅助函数以递增值:

inc_value(#l1{al2=#l2{al3=#l3{value=Value}=L3}=L2}=L1) ->
    L1#l1{al2=L2#l2{al3=L3#l3{value=Value+1}}};
inc_value(R) ->
    R.

请注意最后一个子句,它将与模式不匹配的任何其他内容映射到自身。

让我们定义示例记录来试试这个:

1> R=#l1{foo=1, bar=2}.
#l1{foo = 1,bar = 2,al2 = undefined}

这是一个没有定义完整嵌套的记录。

2>  R1=#l1{foo=1, bar=2, al2=#l2{foo=3, bar=4, al3=#l3{key=mykey, value=10}}}.
#l1{foo = 1,bar = 2,
    al2 = #l2{foo = 3,bar = 4,
              al3 = #l3{key = mykey,value = 10}}}

另一个具有完整结构的人。

尝试帮助函数:

4> inc_value(R). 
#l1{foo = 1,bar = 2,al2 = undefined}

单独留下未完全嵌套的记录。

3> inc_value(R1).
#l1{foo = 1,bar = 2,
    al2 = #l2{foo = 3,bar = 4,
              al3 = #l3{key = mykey,value = 11}}}

它会使完全嵌套的记录增加。

现在列表理解简单易读:

5> [ inc_value(X) || X <- [R, R1] ].
[#l1{foo = 1,bar = 2,al2 = undefined},
 #l1{foo = 1,bar = 2,
     al2 = #l2{foo = 3,bar = 4,
               al3 = #l3{key = mykey,value = 11}}}]

答案 1 :(得分:2)

这是 waaaay 比使用具有破坏性突变的语言更加混乱,但绝对可能。这是污垢:

increment(Records) ->
    [L1#l1{l2 = (L1#l1.l2)#l2{l3 = ((L1#l1.l2)#l2.l3)#l3{value = ((L1#l1.l2)#l2.l3)#l3.value + 1}}} || L1 <- Records].

正如你所看到的,这是丑陋的地狱;此外,很难立即理解这种理解是做什么的。直截了当地弄清楚发生了什么,但我和我店里的任何人都有过这样的话题。简单地累积和反转要好得多 - Erlang编译器和运行时非常适合优化这种模式。

答案 2 :(得分:1)

它并不像看起来那么难。 @Peer Stritzinger给出了一个很好的答案,但这是我的看法,清晰的列表理解:

-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].

答案 3 :(得分:0)

最好的解决方案可能是在函数式编程中研究镜头的概念。 镜头是用于记录变异的功能性getter和setter。如果操作正确,您可以编写高阶镜头撰写原始镜头。

结果是你可以为你的目的构造一个mutator,然后通过理解来运行mutator遍历所有记录。

这是我想有一天为Erlang写的东西之一,但从来没有真正有时间写下来:)