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)
).