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

发布于 2024-11-30 21:32:30 字数 1756 浏览 2 评论 0原文

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

我通过使用lists:partition来过滤掉那些实际需要递增的条目,很容易地处理这个问题,但我仍然无法想出一个可以执行如此简单操作的列表理解。

下面的代码示例可能无法编译 - 它只是为了演示我试图完成的任务。我用“未定义的情况(废话)”部分来说明我原来的问题:

-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, [])).

又名:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

且行且努力 2024-12-07 21:32:30

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

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

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

You can use a list comprehension and even don't need to filter out records that don't have the nesting.

To avoid readability problems I shortened your record definition.

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

Define a helper function to increment the value:

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.

Note the last clause that maps any other stuff that doesn't match the pattern to itself.

Lets define example records to try this out:

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

This is a record that doesn't have the full nesting defined.

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

Another one that has the full structure.

Try out the helper function:

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

It leaves alone the not fully nested record.

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

It increments the fully nested record ok.

Now the list comprehension is simple and readable:

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}}}]
旧街凉风 2024-12-07 21:32:30

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

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编译器和运行时非常擅长优化这种模式。

This is waaaay messier than it would be in a language with destructive mutation, but it is definitely possible. Here's the dirt:

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

As you can see, this is ugly as hell; furthermore, it's difficult to immediately apprehend what this comprehension is doing. It's straightforward to figure out what's going on, but I'd have a talk with anyone in my shop who wrote something like this. Much better to simply accumulate and reverse - the Erlang compiler and runtime are very good at optimizing this sort of pattern.

时光倒影 2024-12-07 21:32:30

它并不像看起来那么难。 @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].

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].
等风也等你 2024-12-07 21:32:30

最好的解决方案可能是研究函数式编程中透镜的概念。 lens 是一个用于记录突变的功能性 getter 和 setter。正确完成后,您就可以编写组成原始透镜的高阶透镜。

结果是,您可以根据您的目的构造一个变体,然后通过理解来运行变体通过所有记录。

这是我有一天想为 Erlang 写的事情之一,但一直没有时间写:)

The best solution is probably to look into the concept of lenses in functional programming. A lens is a functional getter and setter for mutation of records. Done correctly, you can then write higher-order lenses which compose primitive lenses.

The result is that you can construct a mutator for your purpose and then run the mutator through all the records by a comprehension.

It is one of those things I wanna write some day for Erlang but never really got the time to write up :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文