could it also be said that as soon as you make Use of this arrow operator, you switch from declarative to procedural programming?
I don’t think so. Some Prolog purists consider cut harmful because it limits your query to only one result, which can be dangerous when doing database-type stuff. Prolog-inspired SQL-alternative Datalog specifically excludes the ! operator to avoid losing rows which might otherwise match.
A newby mistake I made, and I suspect is quite common, was writing extremely convoluted if ... then ... else if ...
code which got completely unreadable when I started nesting if .. then ...
within outer then/else blocks. I’ve found writing a separate stanza for each or branch made my code way easier to maintain and debug.
A rule of thumb I have is whenever I find myself using the ;
operator, I ask myself I this should be split into a separate stanza of a rule. A big selling point of Prolog is you can do that easily, whereas in other programing languages you end up with lots of cases in a switch block or worse yet nested if.. then ... else...
spaghetti.
thankyou for your information,
prolog code is extremely well suited to modify or enhance after the main code was already written.
where as with other languages you write it once and never touch it again
No, (->)/2 and !/0 are very different, e.g. there is no “else” in a cut, and (->)/2 does not prevent backtracking into another predicate with the same name & arity.
Let me illustrate with a working example:
ifthen1(A, B) :-
( A == B
-> format('A == B')
; format('A \== B')
).
ifthen2(A, B) :-
( A == B
, !, format('A == B')
; format('A \== B')
).
These two work identically. To be clear, I’m not saying the arrow operator is the same as cut. It’s the same as comma cut comma.
To show the difference:
mintest(X, Y, Min) :-
( X @=< Y, !,
Min = X
; Min = Y
).
mintest(_X, _Y, strawberries_and_cream).
?- mintest(1, 2, Min).
Min = 1.
Vs:
mintest(X, Y, Min) :-
( X @=< Y ->
Min = X
; Min = Y
).
mintest(_X, _Y, strawberries_and_cream).
?- mintest(1, 2, Min).
Min = 1 ;
Min = strawberries_and_cream.
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
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.
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.
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.
Do you mean “minus-minus”? Discourse seems to convert this to an “en-dash”.
Thanks for noting. Edited.