You're right that even though your solution works, it isn't very clean nor re-usable as it only works for lists of length 4. Let's try to define a recursive predicate that works for lists of any size.
When looking at both lists one element at a time, there are actually only 2 cases to consider: either the elements are the same or they aren't.
If they are the same, that means that the rest of both lists must have exactly one different element in order to succeed. And that's exactly the predicate we're writing in the first place!
compare([H|T1], [H|T2]) :- compare(T1, T2).
Now for the second case. If the first elements of the list are different, then the rest of both lists must be exactly the same (as we have already encountered a different element
compare([H1|T1], [H2|T1]) :- H1 \= H2.
There, that's all! Now, you may notice output such as this:
?- compare([a,b], [a,c]).
true;
false.
This is because there is still a choice point open: for the first elements the first clause matches, but the second clause hasn't been considered yet. In this case though, we know that both clauses are mutually exclusive. So we can add a cut (!
) to ensure that no choice point is left.
This also allows us to simplify the second close: if we reach this we know the first elements are not the same, so there is no need to check this again.
Putting it all together and the code becomes:
compare([H|T1], [H|T2]) :- !, compare(T1, T2).
compare([_|T], [_|T]).