I'm writing a prolog program to check if a variable is an integer. The way I'm "returning" the result is strange, but I don't think it's important for answering my question.
The Tests
I've written passing unit tests for this behaviour; here they are...
foo_test.pl
:- begin_tests('foo').
:- consult('foo').
test('that_1_is_recognised_as_int') :-
count_ints(1, 1).
test('that_atom_is_not_recognised_as_int') :-
count_ints(arbitrary, 0).
:- end_tests('foo').
:- run_tests.
The Code
And here's the code that passes those tests...
foo.pl
count_ints(X, Answer) :-
integer(X),
Answer is 1.
count_ints(X, Answer) :-
\+ integer(X),
Answer is 0.
The Output
The tests are passing, which is good, but I'm receiving a warning when I run them. Here is the output when running the tests...
?- ['foo_test'].
% foo compiled into plunit_foo 0.00 sec, 3 clauses
% PL-Unit: foo
Warning: /home/brandon/projects/sillybin/prolog/foo_test.pl:11:
/home/brandon/projects/sillybin/prolog/foo_test.pl:4:
PL-Unit: Test that_1_is_recognised_as_int: Test succeeded with choicepoint
. done
% All 2 tests passed
% foo_test compiled 0.03 sec, 1,848 clauses
true.
- I'm using SWI-Prolog (Multi-threaded, 64 bits, Version 6.6.6)
- I have tried combining the two
count_intspredicates into one, using;, but it still produces the same warning. - I'm on Debian 8 (I doubt it makes a difference).
The Question(s)
- What does this warning mean? And...
- How do I prevent it?
First, let us forget the whole testing framework and simply consider the query on the toplevel:
This interaction tells you that after the first solution, a choice point is left. This means that alternatives are left to be tried, and they are tried on backtracking. In this case, there are no further solutions, but the system was not able to tell this before actually trying them.
Using
all/1option for test casesThere are several ways to fix the warning. A straight-forward one is to state the test case like this:
test('that_1_is_recognised_as_int', all(Count = [1])) :- count_ints(1, Count).This implicitly collects all solutions, and then makes a statement about all of them at once.
Using if-then-else
A somewhat more intelligent solution is to make
count_ints/2itself deterministic!One way to do this is using if-then-else, like this:
count_ints(X, Answer) :- ( integer(X) -> Answer = 1 ; Answer = 0 ).We now have:
i.e., the query now succeeds deterministically.
Pure solution: Clean data structures
However, the most elegant solution is to use a clean representation, so that you and the Prolog engine can distinguish all cases by pattern matching.
For example, we could represent integers as
i(N), and everything else asother(T).In this case, I am using the wrappers
i/1andother/1to distinguish the cases.Now we have:
And the test cases could look like:
test('that_1_is_recognised_as_int') :- count_ints(i(1), 1). test('that_atom_is_not_recognised_as_int') :- count_ints(other(arbitrary), 0).This also runs without warnings, and has the significant advantage that the code can actually be used for generating answers:
In comparison, we have with the other versions:
Which, unfortunately, can at best be considered covering only 50% of the possible cases...
Tighter constraints
As Boris correctly points out in the comments, we can make the code even stricter by constraining the argument of
i/1terms to integers. For example, we can write:Now, the argument must be an integer, which becomes clear by queries like:
Note that the example Boris mentioned fails also without such stricter constraints:
Still, it is often useful to add further constraints on arguments, and if you need to reason over integers, CLP(FD) constraints are often a good and general solution to explicitly state type constraints that are otherwise only implicit in your program.
Note that
integer/1did not get the memo:This shows that, although
Xis without a shadow of a doubt constrained to integers in this example,integer(X)still does not succeed. Thus, you cannot use predicates likeinteger/1etc. as a reliable detector of types. It is much better to rely on pattern matching and using constraints to increase the generality of your program.