Which built-in predicate should I use when I want forall/2 behavior but keep backtrackable side-effects?

As SWI-Prolog Manual said, forall/2 is well suited to be used for side effects, but it doesn’t work with backtrackable effects. Details see https://swi-prolog.discourse.group/t/careful-with-forall-2-when-it-is-used-for-side-effects

However, what if I want forall/2 behavior but keep backtrackable side-effect?

For example,

:- use_module(library(chr)).
:- chr_constraint c/2,  r/2.

c(X,Y) <=> Y=X, r(X,Y).

If using forall/2:

?- forall(between(1,3,X), c(X,_)).
true.

If using foreach/2:

?- foreach(between(1,3,X), c(X,_)).
false.

I know why the above two queries return true and false, but what I really require is

?- for_?_(between(1,3,X), c(X,_)).
r(3,3),
r(2,2),
r(1,1).

My current workaround is using findall/3 + maplist/2:

foreach2(Generator, Goal) :-
    findall(Goal, Generator, Goals),
    maplist(call, Goals).

?- foreach2(between(1,3,X), c(X,_))
r(3,3),
r(2,2),
r(1,1).

The question is that

Is there any built-in predicate do the same thing?

Thanks.

findall/3 + maplist/2 would have been my suggestion (or bagof/3 + maplist/2; but that fails if there’s no solution to the goal)

There are also repeat-fail loops (which I don’t like):

?- between(1,3,X), writeln(X), fail ; true.
1
2
3
true.

There is also the repeat/0 predicate for a variant of repeat-fail (see the example in the manual).

Interesting.

No solutions for Goal lead to failure, instead of an empty bag.

which is different from findall/3.

This repeat-fail loop doesn’t fit my needs. It’s like forall2 cannot keep backtrackable side-effects:

?- between(1,3,X), c(X,_), fail ; true.
true.

It would be nice if there is a built-in predicate as the foreach2/2 above. Perhaps it is difficult to give it a good name.

One disadvantage of this foreach2/2 is that it generates an intermediate list of results; the repeat/fail loop doesn’t. But, unless you have a lot of results, this is probably a minor thing.

Side-effects are generally to be avoided in Prolog. :wink:
[I’m currently modifying someone else’s code that has side-effects, and it’s annoying to figure out where the side effects are happening. Side-effects also it make it more difficult to do things like concurrent execution (e.g., using concurrent_maplist/2)

2 Likes

How about using transactions to remove side effects – asserts and retracts – upon failure – i guess, the processing cost is high …

Neither do I :slight_smile: In cases where it was traditionally used I now typically use forall/2. I consider it cleaner and it has a better logical reading. Practically, the whole thing fails if the side effect fails. This tells you something is wrong. Next step, use det/1 or $/1 to declare the side effect to be deterministic for finding the culprit.

3 Likes

I don’t quite understand what said. Could you you explain further?

The main thread expample used CHR instead of asserts and retracts. P.s. CHR could be used as a general purpose language (not just to write constraint system) and side-effects are fine in that language.

Anyway, this is not a big problem, because it could be solved by foreach2/2.

What I’d like to point out here is that Prolog has two kinds of side-effects, i.e. backtrackable and non-backtrackable.

When using a Prolog predicate, if there is no side effect, it is no problem. But if there is, you have to consider what happens for both two kinds of side effects. For example, forall/2 assumes the side effects are non-backtrackable (e.g. IO). If you are using backtrackable side effects (e.g. CHR), the result might not be what you expect. Then you want a “new forall/2” like the foreach2/2 (forall2/2 may be a better name?) to achieve your requirement.

The other way is providing both backtrackable and non-backtrackable operations in CHR (currently it is backtrackable only), but that is another story.

Yes, you are right — i overlooked that its really only about CHR – a system i have no real knowledge about (yet).

I don’t know how CHR handles side effects if at all and if transactions can be part of that …

CHR doesn’t deal with non-logical operations. A CHR clause [in SWI] embeds Prolog as conditional tests and execution, so you can do whatever you like there, but CHR itself only cares about binding variables and tracking CHR constraints. CHR does have the “side-effect” of adding and removing its objects from its database, but these are always done in logical, backtrackable fashion, similar to how CLP manages its relations.

1 Like