I wanted to verify that some predicates have their “primary keys” (in the SQL sense) set up properly … this is the result of 15 minutes of work (with some more time, I could make it a bit easier to use, but I’m lazy). It turns out that subtract/3 doesn’t do quite what I want (it assumes unordered sets but I wanted unordered multisets), so I rolled my own.
%! no_dups(?Tmpl, ^Goal) is semidet.
% Looks for duplicate Tmpl's when backtracking over Goal.
% Fails if Goal has no results.
% Throws an error if duplicate Tmpls.
% e.g.: verify that X,Y results are all unique:
%       no_dups(a(X,Y), Z^pred(X,Y)).
:- meta_predicate no_dups(?, ^).
no_dups(Tmpl, Goal) :-
    bagof(Tmpl, Goal, Results), % fails if Goal fails
    msort(Results, ResultsSorted),
    sort(Results, ResultsSortedNoDups),
    (   ResultsSorted == ResultsSortedNoDups
    ->  true
    ;   list_diff(ResultsSorted, ResultsSortedNoDups, ResultsDiff),
        throw(error(dups(Goal:ResultsDiff), _))
    ).
no_dups(Goal) :-
    throw(error(dups(Goal), _)).
%! list_diff(+List, +Delete, -Result) is det.
% A bit different from library(lists):subtract/3
list_diff(List, [], List) :- !.
list_diff(List, [D|Ds], Result) :-
    (   select(D, List, List2)
    ->  list_diff(List2, Ds, Result)
    ;   list_diff(List, Ds, Result)
    ).