prologb-prolog

Nested loops with accumulators in B-Prolog


B-Prolog has logical loops. For example, that's how we can calculate sum of [1,2,3]:

test1 :-
    foreach(A in 1..3, [], ac(Sa, 0), (
            Sa^1 is Sa^0 + A
        )),
    writeln(sa(Sa)).

?- test1.
test1.
sa(6)
yes

But when I try two nested loops with accumulators, I get errors:

test2 :-
    foreach(_A in 1..3, [Sb], ac(Sa, 0), (
            foreach(B in 1..3, [], ac(Sb, 0), (
                    Sb^1 is Sb^0 + B
                )),
            writeln(sb(Sb)),
            Sa^1 is Sa^0 + Sb
        )),
     writeln(sa(Sa)). 

?- test2.
test2.
*** error(invalid_argument,(+)/2)

Another variant, not including Sb in the list of local variables of the outer loop:

test3 :-
    foreach(_A in 1..3, [], ac(Sa, 0), (
            foreach(B in 1..3, [], ac(Sb, 0), (
                    Sb^1 is Sb^0 + B
                )),
            writeln(sb(Sb)),
            Sa^1 is Sa^0 + Sb
        )),
    writeln(sa(Sa)). 

?- test3.
test3.
sb(6)
*** error(invalid_argument,(+)/2)

Is it even possible to have nested loops with accumulators in B-Prolog?

My B-Prolog version is 8.0#1.


Solution

  • Your program runs correctly when compiled.

    | ?- cl(sergey)

    Compiling::sergey.pl
    ** Warning: Variable 'Sb' is treated as global in foreach (17-25).
    ** Warning: Variable 'Sb' is treated as global in list_comprehension (36-38).
    compiled in 0 milliseconds
    loading...
    
    yes
    | ?- test1
    sa(6)
    
    yes
    | ?- test2
    sb(6)  
    sb(6)
    sb(6)
    sa(18)
    
    yes
    | ?- test3
    sb(6)
    sb(6)
    sb(6)
    sa(18)
    

    There must be some problem with the interpreter. This accumulator thing is very ugly and I never use it. In Picat, the successor of B-Prolog, you can use := to "update" variables.

        test1 =>
            Sa = 0,
            foreach(A in 1..3)
                Sa := Sa+A
            end,
            writeln($sa(Sa)).
    
        test2 =>
            Sa = 0,
            foreach(_A in 1..3)
                Sb := 0,
                foreach(B in 1..3)
                   Sb := Sb+B
                end,
                writeln($sb(Sb)),
                Sa := Sa+Sb
            end,
            writeln($sa(Sa)). 
    

    An even better way is to use list comprehension.

        test1 =>
            Sa = sum([A : A in 1..3]),
            writeln($sa(Sa)).
    
        test2 =>
            Sa = sum([Sb : _A in 1..3, Sb=sum([B : B in 1..3])]),
            writeln($sa(Sa)). 
    

    The compiler compiles the summations into programs that use ':='. As lists are not actually constructed, there is no overhead.