findall, bagof, setof — Collecting All Solutions

Course 2 · Ch 1
findall, bagof, setof — Collecting All Solutions
Turning backtracking's one-at-a-time answers into a single concrete list

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:

parent(tom, bob). parent(tom, liz). parent(bob, ann). parent(bob, pat). parent(liz, jim). ?- parent(tom, X). X = bob ; X = liz.

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(X, parent(tom, X), Children). Children = [bob, liz].

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.

?- findall(X, parent(nobody, X), L). L = [].

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:

?- bagof(Child, parent(Parent, Child), Children). Parent = tom, Children = [bob, liz] ; Parent = bob, Children = [ann, pat] ; Parent = liz, Children = [jim].

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"):

?- bagof(Child, Parent^parent(Parent, Child), AllChildren). AllChildren = [bob, liz, ann, pat, jim].
bagof fails on zero solutions — findall doesn't
Unlike 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.

?- setof(Child, Parent^parent(Parent, Child), Sorted). Sorted = [ann, bob, jim, liz, pat].

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.

PredicateZero solutionsFree variablesOrder
findall/3succeeds, List = []ignored — one combined listsearch order, duplicates kept
bagof/3failsgrouped, unless ^-quantifiedsearch order, duplicates kept
setof/3failsgrouped, unless ^-quantifiedsorted, duplicates removed
Default to findall unless you specifically need grouping or sorting
In real code, 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

Challenge 1

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 solution
Challenge 2

Write 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 solution
Challenge 3

Add 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 solution

Chapter 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^Goal tells 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