Let's simplify the task a bit by first considering is_member/2
.
You write it as:
is_member(X, [Head,Tail]):-
X == Head;
is_member(X, Tail).
Consider how easy it is to misread this as:
is_member(X, [Head,Tail]):-
X == Head,
is_member(X, Tail).
Exercise: What did I change?
For this reason, I recommend a layout like the following:
is_member(X, [Head,Tail]):-
( X == Head
; is_member(X, Tail)
).
Now, on to a few test cases!
First, it is always a good idea to post the most general query. This simply asks: Are there any solutions at all?
?- is_member(E, Ls).
nontermination
That's not a good sign!
So, let us try a few concrete cases. For example, is a
a member of the empty list?
?- is_member(a, []).
false.
That's good! It is what we expected.
Then, is a
a member of the list [a]
?
?- is_member(a, [a]).
false.
That's definitely wrong!
I recommend you start from there, and then move on to more complex definitions. Approach it in a systematic way:
- Write down what ought to hold.
- Try the most general query to see if you can obtain answers from your program.
- Try concrete test cases.
- Think about what the predicate actually means: You can use a predicate in multiple directions, and in many of these cases, the way you described the predicate does not make sense. For example, both the list and the element can already be given, and in that case there is nothing to "add" or "remove".
To locate causes of unexpected failure in your program, use program-slicing, for example by using the following definition to generalize away goals:
:- op(950,fy, *).
*_.
You can now write:
is_member(X, [Head,Tail]):-
( * X == Head
; * is_member(X, Tail)
).
which is a massive generalization of your original program, and in fact declaratively equivalent to:
is_member(X, [Head,Tail]) :- true.
The test case above still fails with this fragment, which shows that the program is still too specific:
?- is_member(a, [a]).
false.