findall, bagof, setof — Collecting All Solutions
Course 1 ended with prolog1-4's backtracking and prolog1-8's cut — both about controlling a search that hands back one solution at a time, via ;. Course 2 opens with the natural next question: what if you want all of them, at once, as an ordinary list you can pass around? That's exactly what findall/3, bagof/3, and setof/3 do — three meta-predicates, genuinely different from each other, that collect a backtracking search's results into one concrete value.
The Problem: Backtracking Gives You Answers One At A Time
Recall the family facts from Course 1:
Each ; press asks Prolog to backtrack and try again. That's fine at the interactive top level, but it's useless inside a program — a predicate that wants "every child of tom, as a list" has no way to press ; for you. Something has to drive the backtracking to exhaustion and gather the results.
findall/3 — The Simplest Collector
findall(Template, Goal, List) runs Goal, collecting Template for every solution found by backtracking, and unifies List with the result — all in one call, no interactive ; needed. This is genuinely close to the mental model Haskell's own list monad gives you (haskell2-3): both collect every result of a nondeterministic computation into one concrete list. The difference is Haskell builds that list through do-notation over [] as a monad instance; Prolog's findall is a dedicated meta-predicate that drives Prolog's own SLD search directly.
findall has one important, very forgiving property: if Goal has no solutions at all, findall still succeeds — with an empty list, not a failure.
bagof/3 — Respecting Free Variables
bagof/3 looks like a drop-in replacement for findall at first glance, but it behaves genuinely differently whenever the goal contains a variable — like Parent below — that isn't in the template:
Instead of one combined list, bagof groups the results by every possible binding of the free variable Parent, backtracking through each group separately. This is a genuine, easy-to-miss surprise coming from findall — the same query, one predicate swapped for another, produces three separate answers instead of one merged list.
To get findall-style behaviour — one combined list, ignoring who the parent was — use the ^ operator to tell bagof to treat Parent as existentially quantified ("for some Parent," not "grouped by Parent"):
findall, bagof genuinely fails (not an empty list) when Goal has no solutions at all: ?- bagof(X, parent(nobody, X), L). reports false, not L = []. A predicate that unconditionally expects a list back can be broken by this — code written against bagof has to account for the goal possibly having zero solutions, or fall back to findall, which always succeeds.
setof/3 — Sorted, Deduplicated Results
setof/3 shares bagof's free-variable grouping behaviour exactly, with two extra guarantees: the resulting list is sorted (Prolog's standard order of terms) and has all duplicates removed.
Compare this to what findall would have produced for the same query — [bob, liz, ann, pat, jim], in whatever order the facts were tried and with any duplicates left in. setof is the right choice specifically when the caller needs a canonical, duplicate-free answer rather than a raw trace of the search.
| Predicate | Zero solutions | Free variables | Order |
|---|---|---|---|
findall/3 | succeeds, List = [] | ignored — one combined list | search order, duplicates kept |
bagof/3 | fails | grouped, unless ^-quantified | search order, duplicates kept |
setof/3 | fails | grouped, unless ^-quantified | sorted, duplicates removed |
findall is usually the safer default precisely because it can't fail out from under you on an empty result — reach for bagof/setof deliberately, when the free-variable grouping or the sorted/deduplicated guarantee is actually the behaviour you want, not by habit.
Coding Challenges
Using the parent/2 facts from this chapter, write a findall/3 query that collects every Parent-Child pair in the whole database as a list of Parent-Child terms (e.g. tom-bob), and show the result.
📄 View solutionWrite a bagof/3 query (without ^) that groups each parent's children separately, then rewrite it with Parent^ to combine every child into one list, and explain in a comment why the two queries produce different-shaped results.
📄 View solutionAdd a duplicate fact (e.g. another parent with an already-listed child) to the database, then write both a findall/3 and a setof/3 query for all children, comparing the two results in a comment to show setof's sorting and deduplication in action.
📄 View solutionChapter 1 Quick Reference
- findall(Template, Goal, List) — collects every Template for every solution of Goal; always succeeds, [] on zero solutions
- bagof(Template, Goal, List) — like findall, but groups results by any free variable in Goal not in Template; fails on zero solutions
- setof(Template, Goal, List) — like bagof, plus sorted order and duplicates removed
Var^Goaltells bagof/setof to treat Var as existentially quantified, combining groups into one list — the findall-shaped behaviour- findall is the safer default for general use; reach for bagof/setof deliberately for grouping or sorted/deduplicated output
- Genuinely comparable in spirit to Haskell's own list monad (haskell2-3) — both collect a nondeterministic computation's results into one concrete list, by very different mechanisms