Thank you again for the detailed answer. If I had to pay you for all the time, you have invested over the years in answering my posts, I would be a poor man.
I understand that the proposed construction is a hook into the runtime system rather than a genuine language feature and the if-rewrite you presented is not sound with the runtime system hook. Before the rewrite could be done safely, the compiler had to check that no undo-actions are added in predicate
handle.
Currently the rewrite is also not sound unconditionally. It is necessary to first check that predicate
handle has mode
procedure. Thus implementing the undo-on-backtrack feature would take to add another precondition to that rewrite.
The feature could but also be implemented without a back-hook: The idea to accomplish that is in short, to let the compiler internally insert the same calls, which I am now adding manually, when I emulate the feature via class
reversion. If that way would however take you less implementation effort, is questionable.
It is the correct behavior that any "bindings" made in the foreach loop in
handle(T) will be reverted at the end of each pass. If it is intended to preserve some result from the loop, it can be done in the usual well known ways.
One way is to instead set up a list, maybe by a list comprehension construct, recursively process the list, and thereby build the result. Alternatively, to preserve results in the foreach loop, results can be stored in a fact, a
varM, or else object.
When the value of a (emulated) reference variable shall be preserved over backtracking, then instead of the original value a deep copy of it must be stored, in which free variables are replaced with fresh variables. That is, because bindings in the original variable could be reverted in backtracking. Moreover the value would change later on when any of the free variables becomes bound.
Thus the foreach construct would work just as usual with the undo-on-backtrack feature. I do not see a problem there.
In an implementation of the undo-on-backtrack feature the try-catch-finally construct (incl. all variants of it) must get a special treatment. In the
reversion class I tried to address the issue in the description of predicate
undoUpTo. I suppose, if you implemented the feature, you would need to treat try-catch-finally similarly:
Code: Select all
S = openStream(),
StackMark = reversion::createUndoStackMark(),
try
handle(T),
canFail()
finally
reversion::undoUpTo(StackMark),
S:close()
end try
Or incl. catching exceptions:
Code: Select all
S = openStream(),
StackMark = reversion::createUndoStackMark(),
try
handle(T),
canFailOrRaiseException()
catch TraceId do
reversion::undoUpTo(StackMark),
handleException(TraceId)
finally
reversion::undoUpTo(StackMark),
S:close()
end try
Basically there exist two sensible backtrack strategies. 1st is, to undo an action automatically on the return to that backtrack point, which had been current, when the action has been done. And 2nd is, to backtrack 'manually' via explicit undo-commands.
Your argument "it is not at all certain that actions should be registered on the current backtrack point, the logically correct backtrack point may be somewhat down in the chain" is not followed by VIP itself (neither by any other Prolog, I suppose). VIP exposes the 1st strategy to users. It is not possible in VIP to let the bindings made in a unification be reverted (or say 'forgotten') some backtrack points down in the chain.
I think, none of the strategies is better than the other in general. Both have their use cases. It is absolutely perfect, that VIP itself employs the 1st strategy. The concept of reference variables also requires to apply the 1st strategy, because the backtracking of the reference variables has to go exactly with VIP's backtracking.
But in other use cases, the 2nd strategy might be the better choice, like you told about the type analysis of the compiler. Depending on the case I am using class
stateManger (i.e. 2nd strategy) or the
reversion class (i.e. 1st strategy - but it suffers from the calls to predicate
backTrackPoint).
B.t.w. some hybrid of 1st and 2nd strategy does not seem appropriate. I guess, the program flow of a hybrid strategy would confuse the programmer (at least me) more than it would benefit.
You may be correct, "this feature will not be used by anyone except me". But people would use it indirectly, if they are supplied with classes, which internally making use of the feature.
A class, which emulates reference variables, would probably find its users, provided it does not need inserting
backTrackPoint calls. Introducing reference variables via a class instead of a built-in feature has the advantage that their internal data structure, which is some kind of tree, is accessible to users. Thus features like for example
anti-unification and
term indexing can be added at the user level.
The attached example objSetM_chain_Demo shall give another idea of a class that internally uses the undo-on-backtrack feature. The example is about the collection type
objSetM_chain. By the undo-on-backtrack feature the modifiable collection type could be turned into persistent one
objSetP_chain whereby the performance characteristics of the modifiable collection are almost preserved. Both variants of the collection can resp. could be used same easily as
setM_redBlack and
setP_redBlack.
A collection of type
objSetM_chain is a set of objects as well as doubly linked list. That is, objects can be inserted before/after other objects and objects can also be tested for membership. The performance characteristics are: Once the needed memory has been allocated, calling any of its predicates (resp. backtracking into a nondeterm one) runs in constant time. Thus it is faster than a red-black tree.
The member objects must support an interface which provides them with a positive ID number. Objects, which support that interface, can be member of arbitrary many
objSetM_chain collections. Conversely the naive construction, in which the object contains a pair of predecessor/successor pointers, enables only the membership of an object in a single chain.
In very principle the specification of a procedure as "has exactly one solution" seems more 'clean' than "succeeds without a backtrack point". Because the former specification is of logical nature, while the later one is of rather technical character. Nevertheless that the specification "has exactly one solution" would internally cause difficulties, it would make VIP look even more sound from the users' perspective.