Reputation: 11
I was given the question:
Define a 2-place predicate 'increment' that holds only when its second argument is an integer one larger than its first argument. For examples, increment(4,5) should hold, but incrment(4,6) should not.
So I wrote:
increment(0, 1) :-
!.
increment(A, B) :-
A is A - 1,
B is B - 1,
increment(A, B).
but this doesn't seem to work as I thought it would. Can someone explain to me what is happening and why this doesn't work correctly, and maybe point me in the right direction?
Upvotes: 1
Views: 105
Reputation: 15328
Here is one which doesn't use is/2
(i.e. forced arithmetic eval of the RHS).
:- use_module(library(clpfd)).
increment(X,Y) :-
assertion((var(X);integer(X))), % nice to check: X is unbound or integer
assertion((var(Y);integer(Y))), % nice to check: X is unbound or integer
assertion((nonvar(X);nonvar(Y))), % nice to check: at least one must be bound
Y #= (X+1). % the poodle's kernel
This is basically the extended version of succ/2
, allowing integers not only in ℕ - most importantly, it can work "both ways": either X
or Y
can be unbound/fresh at call time.
It uses the constraint òperator #=
from library(clpfd)
, which is much more convenient than either is/2
or =:=
, both of which have demands as to what should be on the left and the right of the operator.
I have throw in a few assertion/1
for free. Those often save your bacon and/or are good as "live documentation". I hear that traditionally, one would use calls to must_be/2
instead but I find that brittle and C-like. Passing a whole goal to assertion/1
is exactly what high-level programming should be like!
Test it with plunit
:
:- begin_tests(increment).
test("X is unbound",true(X==21)) :-
increment(X,22).
test("Y is unbound",true(Y==22)) :-
increment(21,Y).
test("X and Y are bound and Y = X+1",true) :-
increment(21,22).
test("X and Y are bound and Y # X+1",fail) :-
increment(10,100).
:- end_tests(increment).
And so:
?- run_tests.
% PL-Unit: increment .... done
% All 4 tests passed
true.
Upvotes: 1
Reputation: 211
How about trying:
increment(X,Y) :- Y is X+1.
I think a problem with the code in the question is that A is A - 1
and B is B - 1
will always fail.
A is A - 1
will fail because whatever number is unified with A
cannot be the same as A
minus 1. It might be that you expected A is A - 1
to reassign A
to A
minus 1 (that would be consistent with how many other programming languages work) but that is not how Prolog works. Once a variable is unified to a term then it stays unified with that term until Prolog backtracks looking for alternative solutions.
Another approach which is closer to what is suggested in the question would be to introduce two new variables that can be used to unify with the results of the subtractions, then use those two variables in the recursive call:
increment(0, 1) :- !.
increment(A, B) :- X is A - 1, Y is B - 1, increment(X, Y).
(I recommend doing increment(X,Y) :- Y is X+1.
instead.)
Upvotes: 3