Bug in 8.4.1 regarding dict

Hi,

the following code shows a bug:

X.crash() := VALUE :- true
 , aggregate_all( sum( M.value ), member( M, X.values), VALUE)
 .

X.workaround() := VALUE :- true
 , findall( V, ( member( M, X.values), V=M.value), VALUES)
 , aggregate_all( sum( M ), member( M, VALUES), VALUE)
 .

test001 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.crash()
 , writeln(VALUE)
 .

test002 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.workaround()
 , writeln(VALUE)
 .

and the output is:



?- test002.
3
true.

?- test001.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [16] throw(error(instantiation_error,_9338))
ERROR:   [14] '.'('<garbage_collected>',value,'<garbage_collected>') at /usr/local/lib/swipl/boot/dicts.pl:46
ERROR:   [13] crash(x{values:[...|...]},_9392) at /home/ox/dev-git/go/endgame-subgames-calculation/003_bug_report.pl:2
ERROR:   [12] eval_dict_function('<garbage_collected>',x,'<garbage_collected>','<garbage_collected>') at /usr/local/lib/swipl/boot/dicts.pl:102
ERROR:   [10] test001 at /home/ox/dev-git/go/endgame-subgames-calculation/003_bug_report.pl:12
ERROR:    [9] toplevel_call('<garbage_collected>') at /usr/local/lib/swipl/boot/toplevel.pl:1117
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

Thanks in Advance
Frank Schwidom

By playing around I found out that already the unification of a variable can cause the error when inside of a term a field dereference of a dict takes place.


X.crash() := VALUE :- true
 , aggregate_all( sum( M.value ), member( M, X.values), VALUE)
 .

X.workaround() := VALUE :- true
 , findall( V, ( member( M, X.values), V=M.value), VALUES)
 , aggregate_all( sum( M ), member( M, VALUES), VALUE)
 .

X.freezes() := VALUE :- true
 , C= aggregate_all( sum( M.value ), member( M, X.values), VALUE)
 , C
 .

X.crash2() := VALUE :- true 
 , TERM = ( member( M, X.values), V=M.value)

 % , findall( V, TERM, VALUES)
 % , VALUE=VALUES

 , VALUE=1

 % , aggregate_all( sum( M ), member( M, VALUES), VALUE)

 % , aggregate_all( sum( V ), TERM, VALUE)
 .

X.crash3() := VALUE :- true 
 , XVALUES= X.values
 , TERM = ( member( M, XVALUES), V=M.value)

 % , findall( V, TERM, VALUES)

 , VALUE=1

 % , aggregate_all( sum( M ), member( M, VALUES), VALUE)

 % , aggregate_all( sum( V ), TERM, VALUE)
 .

test001 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.crash()
 , writeln(VALUE)
 .

test002 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.workaround()
 , writeln(VALUE)
 .

test003 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.freezes()
 , writeln(VALUE)
 .

test004 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.crash2()
 , writeln(VALUE)
 .

test005 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.crash3()
 , writeln(VALUE)
 .

output:

(ins)?- test003. % freezes for a moment and has no output

(ins)?- test004.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [16] throw(error(instantiation_error,_1984))
ERROR:   [13] crash2(x{values:[...|...]},_2010) at /home/ox/dev-git/go/endgame-subgames-calculation/003_bug_report.pl:16
ERROR:   [12] eval_dict_function(crash2(),x,x{values:[...|...]},_2054) at /usr/local/lib/swipl/boot/dicts.pl:102
ERROR:   [10] test004 at /home/ox/dev-git/go/endgame-subgames-calculation/003_bug_report.pl:58
ERROR:    [9] toplevel_call(user:user:test004) at /usr/local/lib/swipl/boot/toplevel.pl:1117
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

(ins)?- test005.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [16] throw(error(instantiation_error,_10796))
ERROR:   [13] crash3(x{values:[...|...]},_10822) at /home/ox/dev-git/go/endgame-subgames-calculation/003_bug_report.pl:29
ERROR:   [12] eval_dict_function(crash3(),x,x{values:[...|...]},_10866) at /usr/local/lib/swipl/boot/dicts.pl:102
ERROR:   [10] test005 at /home/ox/dev-git/go/endgame-subgames-calculation/003_bug_report.pl:63
ERROR:    [9] toplevel_call(user:user:test005) at /usr/local/lib/swipl/boot/toplevel.pl:1117
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

Regards,
Frank

By the simple rule of goal expansion this becomes

.(M, value, VM),
.(X, values, VX),
aggregate_all(sum(VM), member(M, VX), Value).

Which obviously gives the wrong result. I do not see a sensible way to tell the system to translate this into the intended

.(X, values, VX)
aggregate_all(sum(VM), (member(M, VX), .(M,value, VM)), Values).

Of course we could try additional rules. We’d need to know the 2nd argument is a goal. We know this time due to the meta_predicate/1 declaration for aggregate_all. We also need to know that for this case member/2 uses the second argument as input and the first as output. In general it is a relation though, so we cannot know.

I think it is better to keep the rewriting simple (and predictable) and let the user figure out to use a helper predicate to get the expansion order right.

Ok I believe I understand. So I have to write it out.

X.nocrash() := VALUE :- true
 , aggregate_all( sum( X1 ), ( X3 = X.values, member( M, X3), X1 = M.value ), VALUE)
 .


test001 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.nocrash()
 , writeln(VALUE)
 .


output :

(ins)?- test001.
3
true.

But there is still a problem:

when I load the code via ‘-s’ and run it I get the result:

(ins)$ swipl -s 004_expansion_problem.pl 
reading ~/.swiplrc (FS)
Welcome to SWI-Prolog (threaded, 64 bits, version 8.4.1)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

(ins)?- test001.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [16] throw(error(instantiation_error,_20638))
ERROR:   [14] '.'(_20662,value,_20666) at /usr/local/lib/swipl/boot/dicts.pl:46
ERROR:   [13] nocrash(x{values:[...|...]},_20692) at /home/ox/dev-git/swi-prolog/mapping_the_dot/001/004_expansion_problem.pl:2
ERROR:   [12] eval_dict_function(nocrash(),x,x{values:[...|...]},_20736) at /usr/local/lib/swipl/boot/dicts.pl:102
ERROR:   [10] test001 at /home/ox/dev-git/swi-prolog/mapping_the_dot/001/004_expansion_problem.pl:8
ERROR:    [9] toplevel_call(user:user:test001) at /usr/local/lib/swipl/boot/toplevel.pl:1117
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

After just renaming X3 to X4 I get

(ins)?- make.
% /home/ox/dev-git/swi-prolog/mapping_the_dot/001/004_expansion_problem compiled 0.00 sec, 0 clauses
true.

(ins)?- test001.
3
true.

Regards,
Frank

The last 2 attempts I had to rename the variable 2 times and call make to get the effect.

There is an auto-loading dependency. At least in SWI-Prolog 8.5.2 and
likely before. The auto-loading of aggregate_all results in loading of meta-
declaration, and then the expansion is different.

Without pre-loading aggregate_all I get:

?- [user].
X.crash() := VALUE :-  aggregate_all( sum( M.value ), member( M, X.values), VALUE).
X.crash2() := VALUE :-  aggregate_all( sum( H), (member( M, X.values), H = M.value), VALUE).

?- listing(crash).
crash(A, B) :-
    '.'(C, value, D),
    '.'(A, values, E),
    aggregate_all(sum(D), member(C, E), B),
    true.

true.

?- listing(crash2).
crash2(A, B) :-
    '.'(A, values, C),
    '.'(D, value, E),
    aggregate_all(sum(F),
                  ( member(D, C),
                    F=E
                  ),
                  B),
    true.

true.

With pre-loading aggregate all I get:

/* This causes the auto load */
?- aggregate_all(sum(X), (X=1;X=2), S).
S = 3.

?- [user].
X.crash() := VALUE :-  aggregate_all( sum( M.value ), member( M, X.values), VALUE).
X.crash2() := VALUE :-  aggregate_all( sum( H), (member( M, X.values), H = M.value), VALUE).

?- listing(crash).
crash(A, B) :-
    '.'(C, value, D),
    aggregate_all(sum(D),
                  ( '.'(A, values, E),
                    member(C, E)
                  ),
                  B),
    true.

true.

?- listing(crash2).
crash2(A, B) :-
    aggregate_all(sum(C),
                  ( '.'(A, values, D),
                    member(E, D),
                    '.'(E, value, F),
                    C=F
                  ),
                  B),
    true.

true.

Hope this helps!

Ok, but now I have the next problem.

How can I avoid the need to export the nocrash predicate ?

% problem : nocrash has to be exported
:- module( test, [test001/0, nocrash/2 ]).

% :- aggregate_all( sum(X), member(X, [1,2]), _). % loads module aggregate

:- use_module(library(aggregate)).

X.nocrash() := VALUE :- true
 , aggregate_all( sum( X1 ), ( X5 = X.values, member( M, X5), X1 = M.value ), VALUE)
 .

test001 :- true
 , VALUE = x{values:[p{value:1},p{value:2}]}.nocrash()
 , writeln(VALUE)
 .

Thanks for your answers so far. The topic is for me almost solved in the way that I have to load a specific library to avoid the wrong term rewriting order.

But I am not sure to keep the issue open because lt keeps opaque to me in which order which libraries are loaded and how the bug appears and how to avoid it in the future because the errormessage itself - if it happens - don’t really clarify the origin of the misbehaviour.

Maybe there should be library dependency description anywhere so the bug cannot happen anymore undependendly whether I have loaded a lib or it gets autoloaded.

Regards,
Frank

The binding of functions on dicts is provided by the dict tag that is mapped to the module.

Possibly. Interaction between term/goal rewriting is typically hard. Several systems have partial solutions, but all pretty different. I’ve been working on a proper solution, but this project stalled due to lack of urgency and time.

Typically autoloading is problematic for meta-predicates and libraries involving term/goal rewriting. We could easily detect the first. The latter is a bit harder to do reliably :frowning: