scryer-prolog icon indicating copy to clipboard operation
scryer-prolog copied to clipboard

copy_term/3 kind of removes constraint

Open UWN opened this issue 3 years ago • 2 comments

The idea of copy_term/3 is to leave the 'constraint store' completely unmodified. But this is what happens:

?- userpred.
caught: error(existence_error(procedure,userpred/0),userpred/0) % expected
?- freeze(X,userpred).
   freeze:freeze(X,user:userpred). % expected
?- freeze(X,userpred), copy_term(X,C,Rs).
   Rs = [freeze:freeze(C,user:userpred)]. % unexpected, missing above
?- freeze(X,userpred), copy_term(X,C,Rs), X = 1.
   X = 1, Rs = [freeze:freeze(C,user:userpred)]. % unexpected, missing below error
?- freeze(X,userpred), X = 1.
caught: error(existence_error(procedure,userpred/0),userpred/0) % expected

UWN avatar Feb 08 '22 09:02 UWN

copy_term/3 should wrap the gathering of residual goals in findall/3 in order to undo any modifications that the gathering itself performs on attributes.

Conceptually, copy_term/3 should be written like this:

copy_term(Term, Copy, Gs) :-
        '$term_attributed_variables'(Term, Vs),
        findall(Term-Gs,
                ( phrase(gather_residual_goals(Vs), Gs),
                  '$delete_all_attributes'(Term)
                ),
                [Copy-Gs]).

We assume here that a nonterminal gather_residual_goals//1 is defined that invokes M:attribute_goals//1 for each variable in the argument with M being instantiated to the respective module that any attribute on this variable stems from.

The reason to use findall/3 is that M:attribute_goals//1 may destructively modify an attribute (or remove it) in order to simplify the projected goals, and to indicate that a residual goal was already generated for a particular constraint that may affect several variables yet needs to be stated only once, such as all_distinct/1.

Also, we assume the presence of an internal predicate called '$delete_all_attributes'/1 which deletes all attributes from any attributed variables in the given term. This is used to remove the constraints from the copy (or rather, from the term that is then copied, and which has its attributes reinstantiated by the backtracking that occurs due to findall/3).

triska avatar Feb 09 '22 20:02 triska

For the moment, the defective copy_term(T,T,[]) can be replaced by \+ \+ copy_term(T,T,[]).

UWN avatar Feb 12 '22 09:02 UWN

Regarding sort/2, please see this comment: https://github.com/mthom/scryer-prolog/commit/8a52ba09cedabae1e30c749c5094ea1751d25653#r101125012

triska avatar Feb 18 '23 11:02 triska

It seems to be less resource consuming to put '$term_attributed_variables'(Term, Vs), into the findall/3, this reclaims the space for Vs.

UWN avatar Feb 18 '23 14:02 UWN

copy_term(Term, Copy, Gs) :-
   can_be(list, Gs),
   findall(Term-Rs, term_residual_goals(Term,Rs), [Copy-Gs]).

term_residual_goals(Term,Rs) :-
   '$term_attributed_variables'(Term, Vs),
   phrase(gather_residual_goals(Vs), Rs),
   '$delete_all_attributes'(Term).    % isn't Vs better?

UWN avatar Feb 18 '23 15:02 UWN

'$delete_all_attributes'(Term). % isn't Vs better?

Yes, and on a general note, I would like to add that it is highly desirable to keep as much code as possible in Prolog, because only then will there be the greatest motivation to actually improve the performance of the Prolog code. Putting it all in Rust is tempting, but should be seen as a last resort: The key design point is finding exactly the smallest set of primitives that absolutely must be placed in Rust for acceptable performance, or to enable bootstrapping.

In this concrete case, it seems preferable to implement the internal Rust-based '$delete_all_attributes'/1 so that it works for a single variable, and then use it for all Vs that were gathered by a previously used '$term_attributed_variables'/1.

triska avatar Feb 18 '23 17:02 triska

What about the deeper question, whether or not findall/3 should copy attributes at all. So far, SICStus never copies constraints and for those who need it, copy_term/3 was provided. That was the very purpose of it.

?- X in 1..2.
   clpz:(X in 1..2).
?- findall(X,X in 1..2,Xs).
   Xs = [_A], clpz:(_A in 1..2).
?- X in 1..2, copy_term(X,Y).
   clpz:(X in 1..2), clpz:(Y in 1..2).
?- X in 1..2, asserta(fact(X)), fact(Y).
   clpz:(X in 1..2).
?- setof(X,(X in 1..2 ; X in 2..3), Xs).
   Xs = [_A,_B], clpz:(_A in 1..2), clpz:(_B in 2..3).
?- setof(X,(X in 1..3, (X in 1..2 ; X in 2..3)), Xs).
   Xs = [_A,_B], clpz:(_A in 1..2), clpz:(_B in 2..3).
?- setof(X,(X in 1..2 ; X in 1..2), Xs).
   Xs = [_A,_B], clpz:(_A in 1..2), clpz:(_B in 1..2).
?- catch((X in 1..2, throw(ex(X))), Pat, true).
   Pat = ex(_A), clpz:(_A in 1..2).

UWN avatar Feb 19 '23 08:02 UWN

I think findall/3 is worth discussing in a separate issue? In this way, we can focus on copy_term/3 here, does it now work as expected and can we close this issue and the related issue #923?

triska avatar Feb 19 '23 09:02 triska