Nested loops with accumulators in B-Prolog

2019-07-04 00:09发布

问题:

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.

回答1:

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.



回答2:

http://www.probp.com/manual/node55.html seems to show a nested loop, flattened:

?-foreach(A in [a,b], I in 1..2, ac(L,[]), L^1=[(A,I)|L^0]).
L = [(b,2),(b,1),(a,2),(a,1)]

We just need to recover the end-of-inner loop condition. See if this works:

test2 :-
    foreach(A in 1..3,  
            B in 1..3, [], [ac(Sa, 0), ac(La, []) ac(Sb, 0)], (
                    (  ( La^0 = [] ; La^0 = [A] )  % or just (La^0 = [A])
                    ->                             % same A, next B
                       Sa^1 is Sa^0, Sb^1 is Sb^0 + B
                    ;                              % new A, B-loop has ended
                       Sa^1 is Sa^0 + Sb^0, writeln(sb(Sb^0)), Sb^1 is 0 + B
                    ),
                    La^1 = [A]
                )),
     Sa_final = Sa + Sb,                           % process the final iteration
     writeln(sa(Sa_final)). 

If there can be repetitions in As, just index them up as the docs show:

?-foreach((A,I) in ([a,b],1..2), ac(L,[]), L^1=[(A,I)|L^0]).
L = [(b,2),(a,1)]

and break on the changes in I.

(Not tested).