Programming cooperation

Usually, this would be written
setof(C, A^B^(my_module:my_goal(A,B,C)), Set).

It’s easy to get the “exists” items wrong (and even easier if there are anonymous variables, or “joining” variables), so I would suggest:

my_goal(C) :- my_module:my_goal(_A,_B,C)
... setof(C, my_goal(C), Set)

(also, if you import my_goal, then you don’t need my_module:...)

As to differences between findall/3 and bagof/3 (besides the existential variables), here are examples where findall/3 breaks “pure logic” (e.g., you can define not/1 or var/1 using findall/3):

This doesn’t mean that findall/3 shouldn’t be used, but you need to be aware of the same kinds of things that cause problems with “not” or negation-as failure. I prefer to use this instead of findall:
( findall(C, my_goal(C), set) -> true ; C = [] )

Your experience is very different from mine … every place I’ve worked, there’s been lots of cooperation between programmers, sometimes even with competitors (e.g., setting industry standards). [There have been some nasty people who took advantage of the cooperation, but that happens outside of commercial contexts also.]

Thanks for introducing me to a subtlety of the arrow operator I wasn’t aware of @brebs.

The same thing can easily be done without the arrow operator as follows:

mintest(X, Y, X) :-
    X @=< Y.

mintest(X, Y, Y) :-
    X @> Y.

mintest(_X, _Y, strawberries_and_cream).

So my prejudice that -> is pointless remains.

The thing is: in your example, you need two tests, one for smaller than, another one for larger than, which can become computationally expensive if the test ist more complicated.

The simple way to avoid a second computation is to cut:

mintest(X, Y, X) :-
    X @=< Y, !.

mintest(_X, Y, Y).

The example @brebs gave explained that -> doesn’t cut (as I had wrongly thought), but rather leaves the way open to further options. I maintain a stanza per option is better style, and ideally each option should have its own clearly defined guard as Edsger Dijkstra proposed.

Ah, of course, sorry. Your example referred to ->, not cut. I missed that point.

Another thing: I think the unification should be postponed after the cut,

mintest(X, Y, Min) :-
    X @=< Y,
    !, Min = X.

mintest(_X, Y, Y).

I don’t remember exactly why, but O’Keefe explained it in his book.

Which brings us to SSU:

mintest(X, Y, Min),
    X @=< Y
 => Min = X.

mintest(_X, Y, Min)
 => Min = Y.

aka. Prolog for Turbo Pascal :slight_smile:

basically what happened is this:

from 1997 to 2008 i programmed and sold software, created with turbo-prolog / visual-prolog, to gyms and fysiotherapists.

in 2007 it was decided by the management that all the software
had to be rewritten in/to dot-net, ( in visual-basic or C hash ) , this was
without my cooperation.

after all they couldnt realize this, and in 2014 the company went Bankrupt, after which 1 coworker, who had just been on a self invented sickness vacation for 1.5 year, came back, took all the
customer adresses and went to a similar company in the market where he had a new job with all the contacts.

0.5 year later i bought the bankrupt company and i enhanced the software and the graphics to what it is now.

1 Like

I have tested, which seems to support O. Keefe, the reason is not clear for me though.

mintest(X, Y, Min) :-
    X @=< Y,
    !, Min = X.
mintest(_X, Y, Y).

x_mintest(X, Y, X) :-  X @=< Y, !.
x_mintest(_X, Y, Y).

% ?- findall(data(I, J, K), (between(0,3, I), between(0,3,J), between(0,3,K), mintest(I, J, K)), S), length(S, L), maplist(writeln, S).
%@ data(0,0,0)
%@ data(0,1,0)
%@ data(0,2,0)
%@ data(0,3,0)
%@ data(1,0,0)
%@ data(1,1,1)
%@ data(1,2,1)
%@ data(1,3,1)
%@ data(2,0,0)
%@ data(2,1,1)
%@ data(2,2,2)
%@ data(2,3,2)
%@ data(3,0,0)
%@ data(3,1,1)
%@ data(3,2,2)
%@ data(3,3,3)
%@ S = [data(0, 0, 0), data(0, 1, 0), data(0, 2, 0), data(0, 3, 0), data(1, 0, 0), data(1, 1, 1), data(1, 2, 1), data(1, 3, 1), data(..., ..., ...)|...],
%@ L = 16.


% ?- findall(data(I, J, K), (between(0,3, I), between(0,3,J), between(0,3,K), x_mintest(I, J, K)), S), length(S, L), maplist(writeln, S).
%@ data(0,0,0)
%@ data(0,1,0)
%@ data(0,1,1)
%@ data(0,2,0)
%@ data(0,2,2)
%@ data(0,3,0)
%@ data(0,3,3)
%@ data(1,0,0)
%@ data(1,1,1)
%@ data(1,2,1)
%@ data(1,2,2)
%@ data(1,3,1)
%@ data(1,3,3)
%@ data(2,0,0)
%@ data(2,1,1)
%@ data(2,2,2)
%@ data(2,3,2)
%@ data(2,3,3)
%@ data(3,0,0)
%@ data(3,1,1)
%@ data(3,2,2)
%@ data(3,3,3)
%@ S = [data(0, 0, 0), data(0, 1, 0), data(0, 1, 1), data(0, 2, 0), data(0, 2, 2), data(0, 3, 0), data(0, 3, 3), data(1, 0, 0), data(..., ..., ...)|...],
%@ L = 22.

With your example, I remember the reason: x_mintest(1, 3, 3) does not unify with the head of the first clause, since that requires the 1st and the 3rd argument to be equal (both X). Therefore, the body with the cut is skipped, and the second rule is applied.

I’d say that’s an example of the importance of mode indicators in documentation, ie it needs to be made clear to users of x_mintest that it only works correctly if the third argument is a variable which will return the min value.

%!      x_mintest(+X, +Y, -Min)  is det
%       Sets Min to the minimum of X and Y
x_mintest(X, Y, X) :-  X @=< Y, !.
x_mintest(_X, Y, Y).

If the final value is supplied, it wrongly says x_mintest(0,1,1) etc is true. So to make the predicate bidirectional (always a good thing)

%!      mintest(+X, +Y, ?Min)  is semidet
%       Sets Min to the minimum of X and Y, or checks if Min is the correct min value
mintest(X, Y, Min) :-
    X @=< Y,
    !, Min = X.
mintest(_X, Y, Y).

The effect of -> (if-then) is local to a single clause, but the effect of ! (cut) is global to the entire predicate. Therefore, -> is preferable. It’s far from perfect – it has some of the “procedural vs decalrative” problems that cut has. The example given by @brebs (Programming cooperation - #20 by brebs) illustrates the difference.

As mentioned by @mgondan1 , the => notation (single-sided unification) can be a nice alternative to ->.

There’s also a *-> operator (“soft cut”) that is sometimes useful. It allows backtracking of the condition whereas -> succeeds at most once.

2 Likes

Note that all built-in and public library predicates of SWI-Prolog are supposed to be stead fast, i.e., they do not generate logically incorrect result when used with an instantiated “output” argument. I think that is good practice. Internal predicates inside modules may not be steadfast if we know they are called with a specific mode.

In SWI-Prolog docs, any argument mentioned as - is an output argument and the behavior when called with an instantiated argument should be the same as calling it with an uninstantiated argument followed by a unification. Thus a declaration as

%!   p(-X) is det.

implies that p(a) behaves as p(X), X = a and thus is in fact semidet.

Predicates that require an uninstantiated argument should be documented as --. This notably concerns foreign predicates that create some foreign object and make this available as a blob.

2 Likes

Do you mean “minus-minus”? Discourse seems to convert this to an “en-dash”.

Thanks for noting. Edited.

The operator priorities are wrong for that:

?- display(A^m:p(A)).
:(^(_56826,m),p(_56826))

Creating a call to an illegal module. Thus, one must write

 setof(Template, Vars^(Module:Goal), Result)

Or import the predicate …

I’ve edited my post for posterity :wink:

It could also be written:
my_module:setof(C, A^B^my_goal(A,B,C), Set)