Please let us take a brief step back and consider a few issues that are more general than how to use one or two specific predicates such as !/0
or not/1
.
First, let us fix a few syntax issues, and let your original code read:
choose(0,_,[]) :- !.
choose(N,[H|T], [H|R]) :- M is N-1, choose(M,T,R).
choose(N, [_|T],R) :- choose(N,T,R).
chooseAll(N,L,Res) :-
chooseAll(N,L,[],Res).
chooseAll(N,L,Seen,Res):-
choose(N,L,R),
not(member(R,Seen)),
!,
chooseAll(N,L,[R|Seen],Res).
chooseAll(_,_,Res,Res).
Starting from this, I apply the following two minor changes:
- Instead of
not/1
, I use (\+)/1
, because (\+)/1
is ISO but not/1
is, well, not.
- Instead of
(is)/2
, I use the CLP(FD) constraint (#=)/2
to advertise more recent language constructs and make explicit that we are reasoning only over integers in this case, not for example floats or other kinds of numbers. Think of this as guaranteeing additional type safety.
We these two small changes we obtain:
choose(0,_,[]) :- !.
choose(N,[H|T], [H|R]) :- M #= N-1, choose(M,T,R).
choose(N, [_|T],R) :- choose(N,T,R).
chooseAll(N,L,Res) :-
chooseAll(N,L,[],Res).
chooseAll(N,L,Seen,Res):-
choose(N,L,R),
\+ member(R,Seen),
!,
chooseAll(N,L,[R|Seen],Res).
chooseAll(_,_,Res,Res).
Now let's start! I am eager to try out the main predicate by asking: Which solutions are there at all?
To find out, I post the so-called most general query, where all arguments are fresh variables:
?- chooseAll(X, Y, Z).
X = 0,
Z = [[]].
What? There is only a single solution of this predicate, right? Right?
Probably not.
So, I would like to get more answers for such basic questions. To get them, I make the following additional changes:
- I remove the
!/0
.
- I use
dif/2
to state that two terms are different.
- I slightly reorder the clauses.
- I add the constraints
N #> 0
for those clauses that only apply if N
is greater than 0.
So, we have:
choose(0,_,[]).
choose(N,[H|T], [H|R]) :- N #> 0, M #= N-1, choose(M,T,R).
choose(N, [_|T],R) :- N #> 0, choose(N,T,R).
chooseAll(N,L,Res) :-
chooseAll(N,L,[],Res).
chooseAll(_,_,Res,Res).
chooseAll(N,L,Seen,Res):-
choose(N,L,R),
maplist(dif(R), Seen),
chooseAll(N,L,[R|Seen],Res).
And now we have for example:
?- chooseAll(X, Y, Z).
Z = [] ;
X = 0,
Z = [[]] ;
X = 1,
Y = [_1966|_1968],
Z = [[_1966]] ;
X = 1,
Y = [_3214, _3220|_3222],
Z = [[_3220], [_3214]],
dif(_3220, _3214) ;
X = 1,
Y = [_3560, _3566, _3572|_3574],
Z = [[_3572], [_3566], [_3560]],
dif(_3572, _3560),
dif(_3572, _3566),
dif(_3566, _3560) .
X = 0,
Z = [[]].
I leave it as an exercise to figure out if this program is now too general, too specific or both, and to either add or remove the necessary constraints to obtain only the desired answers.
The main point I wanted to show is that sticking to pure predicates helps you to obtain much more general programs. Using !/0
and (\+)/1
doesn't help with that. To avoid backtracking in specific cases while retaining the predicate's generality, use for example the new predicate if_/3
.
choose (0,_,[]) :- !.
is invalid syntax. there mustn't be a space between a predicate name and the opening parenthesis following it. – Will Ness