Generating numbers and collecting answers
Goals that invent values with between, the failure-driven loop that prints one line per answer, and findall, forall and once to keep the output under control.
Goals that offer answers
Some goals invent values instead of checking them. between(1, 4, N) makes N stand for 1, then 2, then 3, then 4, offering one answer at a time. That is how a counted loop is written here.
It is also why a rule that prints inside that search prints once per answer. If you would rather have one tidy list at the end, collect the answers with findall.
between works as a test as well as a generator. between(1, 4, 3) succeeds immediately, and between(1, 4, 9) fails; the same goal checks a bound value instead of offering new ones. That dual use is common in Prolog: a predicate describes a relation, and which direction it runs depends on what is already bound.
Loops that fail, and lists of answers
The classic loop is a rule that prints and then fails on purpose. The failure sends the engine back for the next answer, and when the generator runs dry the rule fails too — which is why that rule gets a second clause that does nothing and succeeds, so the rest of the program can carry on.
findall(Template, Goal, List) runs a goal, collects every answer for the template and hands you the list. forall(Generator, Action) runs an action for each answer and stays deterministic. once(Goal) keeps only the first answer.
findall runs the goal to exhaustion, so it never leaves a choice point behind. It copies each answer for the template, which means the list holds values rather than references to variables that later change. The price is memory: an infinite generator makes findall run forever, and a very large answer set is held in full before you can print it.
Failure is a control-flow tool
In most languages a failed test ends a branch. Here it is a request for another answer. The failing loop ends a clause with fail, which sends the engine back into the generator for the next value; when the generator has no more, the whole clause fails, and the second clause catches that failure.
That second clause is not decoration. Without it, the rule fails after printing its last line and takes the rest of main with it. A clause such as show(_, _). succeeds with no work, which lets the program continue after the loop. findall is the quieter alternative: it runs a goal to exhaustion without printing, gathers each answer into a list, and succeeds once.
Backtracking is what makes the first form work: each answer is produced on demand, and the engine only looks for the next one when the caller asks.
A trace of the failing loop shows why the stop clause is needed. show(_, _) calls square, prints a line, then fails. The failure sends the engine back to square, which offers the next N; after the fourth answer, square has no more, so the first clause of show fails. The second clause then matches, succeeds with no output, and lets main continue.
What goes wrong
A generator in a position that expects one answer makes the surrounding goal run once per answer. Printing inside the loop is fine when you want to watch the search, but a write that was meant as a summary prints repeatedly. Wrap the work in findall or forall when the output should be a single result.
A failing loop with no stop clause fails after its last line, and that failure travels out of the rule and takes the rest of main with it. The stop clause is not decoration; it is the goal that turns exhaustion into success. And findall keeps duplicates, so a repeated answer appears twice in the list.
Collect, act on each, take the first
% Collect answers, act on each one, take just the first.
main :-
findall(N, between(1, 4, N), Ns),
write('all '), write(Ns), nl,
forall(between(1, 3, K),
( S is K * 10, write(S), nl )),
once(between(5, 9, O)),
write('first only '), write(O), nl.
:- initialization(main).A loop of four answers
Run it and count the lines above the list. Then change the range to 1 to 6 and watch the loop and the list grow together.
Three mistakes to watch for. Calling a generator in a context that expects one answer makes the surrounding goal run once per answer. A failing loop with no stop clause fails after its last line and takes the rest of the program with it. And findall does not remove duplicates; it keeps one entry per answer, so a repeated answer appears twice in the list.
Try these next
- Collect the squares of 1 to 6 with findall and print the list.
- Write a failing loop that prints only the even numbers from 1 to 10.
- Use once to take the first answer of a generator and compare it with the whole list.
Only the even squares
Run it and read the list and the loop. Then change the first range to 1 to 12 and check which numbers join the list.
A goal that writes inside a looping rule prints once per solution, which is fine when you want to watch the search and confusing when you want one answer. Wrap the work in findall or forall for a controlled result.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does between(1, 4, N) do?
