First things first: the documentation of the SWI-Prolog Prolog Unit Tests package is quite good. The different modes are explained in Section 2.2. Writing the test body. The relevant sentence in 2.2.1 is:
Deterministic predicates are predicates that must succeed exactly once and, for well behaved predicates, leave no choicepoints. [emphasis mine]
What is a choice point?
In procedural programming, when you call a function, it can return a value, or a set of values; it can modify state (local or global); whatever it does, it will do it exactly once.
In Prolog, when you evaluate a predicate, a proof tree is searched for solutions. It is possible that there is more than one solution! Say you use between/3
like this:
For x = 1, is x in [0, 1, 2]?
?- between(0, 2, 1).
true.
But you can also ask:
Enumerate all x such that x is in [0, 1, 2].
?- between(0, 2, X).
X = 0 ;
X = 1 ;
X = 2.
After you get the first solution, X = 0
, Prolog stops and waits; this means:
The query between(0, 2, X)
has at least one solution, X = 0
. It might have further solutions; press ;
and Prolog will search the proof tree for the next solution.
The choice point is the mark that Prolog puts in the search tree after finding a solution. It will resume the search for the next solution from that mark.
The warning "Test succeeded with choicepoint" means:
The solution Prolog found was the solution the test expected; however, there it leaves behind a choice point, so it is not "well-behaved".
Are choice points a problem?
Choice points you didn't put there on purpose could be a problem. Without going into detail, they can prevent certain optimizations and create inefficiencies. That's kind of OK, but sometimes only the first solution is the solution you (the programmer) intended, and a next solution can be misleading or wrong. Or, famously, after giving you one useful answer, Prolog can go into an infinite loop.
Again, this is fine if you know it: you just never ask for more than one solution when you evaluate this predicate. You can wrap it in once/1
, like this:
?- once( between(0, 2, X) ).
or
?- once( count_ints(X, Answer) ).
If someone else uses your code though all bets are off. Succeeding with a choice point can mean anything from "there are other useful solutions" to "no more solutions, this will now fail" to "other solutions, but not the kind you wanted" to "going into an infinite loop now!"
Getting rid of choice points
To the particular example: You have a built-in, integer/1
, which will succeed or fail without leaving choice points. So, these two clauses from your original definition of count_ints/2
are mutually exclusive for any value of X
:
count_ints(X, Answer) :-
integer(X), ...
count_ints(X, Answer) :-
\+ integer(X), ...
However, Prolog doesn't know that. It only looks at the clause heads and those two are identical:
count_ints(X, Answer) :- ...
count_ints(X, Answer) :- ...
The two heads are identical, Prolog doesn't look any further that the clause head to decide whether the other clause is worth trying, so it tries the second clause even if the first argument is indeed an integer (this is the "choice point" in the warning you get), and invariably fails.
Since you know that the two clauses are mutually exclusive, it is safe to tell Prolog to forget about the other clause. You can use once/1
, as show above. You can also cut the remainder of the proof tree when the first argument is indeed an integer:
count_ints(X, 1) :- integer(X), !.
count_ints(_, 0).
The exactly same operational semantics, but maybe easier for the Prolog compiler to optimize:
count_ints(X, Answer) :-
( integer(X)
-> Answer = 1
; Answer = 0
).
... as in the answer by mat. As for using pattern matching, it's all good, but if the X
comes from somewhere else, and not from the code you have written yourself, you will still have to make this check at some point. You end up with something like:
variable_tagged(X, T) :-
( integer(X) -> T = i(X)
; float(X) -> T = f(X)
; atom(X) -> T = a(X)
; var(X) -> T = v(X)
% and so on
; T = other(X)
).
At that point you can write your count_ints/2
as suggested by mat, and Prolog will know by looking at the clause heads that your two clauses are mutually exclusive.
I once asked a question that boils down to the same Prolog behaviour and how to deal with it. The answer by mat recommends the same approach. The comment by mat to my comment below the answer is just as important as the answer itself (if you are writing real programs at least).