The short answer is, no, nothing really "nicer" exists, in terms of being always correct, easy to implement, and efficient all at the same time.
A small correction: it is enough to write
data(MaxID, MaxName, MaxAge),
\+ (data(ID, Name, Age), MaxAge < Age)
(as suggested by @false in the comment to your question)
You can see also this question and answers. This answer goes into some detail when and why using setof/3
can be problematic. It might be important for your use case.
Another way to do it (not mentioned in the very useful answer by @CapelliC) is to collect all solutions from your predicate and sort them, using a key. If you are using SWI-Prolog, or another Prolog that has the 4-argument version of sort that lets you choose the comparison and the key within a term, you could do, for example:
bagof(data(ID, Name, Age), data(ID, Name, Age), All),
sort(3, @>=, All, [data(Max_ID, Max_Name, Max_Age)|_])
As long as your data/3
is just a table of facts, this is safe to use.
This of course breaks down if you have equivalent but not equal elements in the list, as data(10, john, 34)
and data(101, jane, 34)
. In the question and answer I linked there are examples how to deal with this, but again, I really don't think it gets any "nicer". It could be more efficient. I strongly recommend considering your use case carefully, and measuring performance if you think this can be a bottle neck.
Looking into the implementation of library(aggregate) suggested by @CapelliC it is obvious that it was meant for that use case exactly: it can find minimum, maximum, sum and so on in constant memory and touching each fact only once, and falls back to building the whole list if necessary.