How use erlang:setelement/3 in tail recursive function at r23+

1.r23 +
2.fun setelement_test:test_c/2 how do it?
3.at r22, test_c/2 and test_d/2 results are the same.


-module(setelement_test).
-export([
    test_a/2,
    test_b/2,
    test_c/2,
    test_d/2,
    test/0
]).
-record(r_element, {key1, key2, key3}).

test_a([], Element) -> Element;
test_a([{Index, AddVal} | T], Element) ->
    #r_element{} = Element,
    OldVal = element(Index, Element),
    Element2 = setelement(Index, Element, OldVal + AddVal),
    test_a(T, Element2).

test_b([], Element) -> Element;
test_b([{Index, AddVal} | T], Element) ->
    OldVal = element(Index, Element),
    Element2 = setelement(Index, Element, OldVal + AddVal),
    test_b(T, Element2).

test_c([], Element) -> Element;
test_c([{IndexName, AddVal} | T], Element) ->
    #r_element{} = Element,                                   %% diff and test_d/2
    Element2 =
        case to_index(IndexName) of
            Index when Index > 0 ->
                OldVal = element(Index, Element),
                setelement(Index, Element, OldVal + AddVal);  %% {call_ext_last,3,{extfunc,erlang,setelement,3},3},
            0 ->
                Element
        end,
    test_c(T, Element2).

test_d([], Element) -> Element;
test_d([{IndexName, AddVal} | T], Element) ->
    Element2 =
        case to_index(IndexName) of
            Index when Index > 0 ->
                OldVal = element(Index, Element),
                setelement(Index, Element, OldVal + AddVal);  %% {call_ext,3,{extfunc,erlang,setelement,3}},
            0 ->
                Element
        end,
    test_d(T, Element2).

to_index(a) -> #r_element.key1;
to_index(b) -> #r_element.key2;
to_index(c) -> #r_element.key3;
to_index(_) -> 0.

test() ->
    Element = #r_element{key1 = 10, key2 = 20, key3 = 30},
    ListA = [{2, 1}, {3, 2}, {4, 3}, {2, 11}],
    A = test_a(ListA, Element),
    io:format("A=~w~n",[A]),                      %% A={r_element,22,22,33}
    B = test_b(ListA, Element),
    io:format("B=~w~n",[B]),                      %% B={r_element,22,22,33}
    ListB = [{a, 1}, {b, 2}, {d, 3}, {c, 11}],
    C = test_c(ListB, Element),
    io:format("C=~w~n",[C]),                      %% C={r_element,11,20,30} ??
    D = test_d(ListB, Element),
    io:format("D=~w~n",[D]),                      %% D={r_element,11,22,41}
    ok.


test

c(setelement_test, [debug_info]).
setelement_test:test().
5 Likes

I can confirm that this is a bug in the compiler.

The bug is fixed for the upcoming Erlang/OTP 25 release.

One way to avoid triggering the bug is to rewrite test_c/2 and to_index/1 like this:

test_c([], Element) -> Element;
test_c([{IndexName, AddVal} | T], Element) ->
    #r_element{} = Element,
    Element2 =
        case to_index(IndexName) of
            none ->
                Element;
            Index ->
                OldVal = element(Index, Element),
                setelement(Index, Element, OldVal + AddVal)
        end,
    test_c(T, Element2).

to_index(a) -> #r_element.key1;
to_index(b) -> #r_element.key2;
to_index(c) -> #r_element.key3;
to_index(_) -> none.
4 Likes

By the way, for this use case it would probably be better to use a map instead of a record.

2 Likes

Thanks, I didn’t read the issues in detail. :sweat_smile:

3 Likes