This notebook is not running. The page loaded but its JavaScript did not. The usual cause is opening index.html straight from disk — browsers refuse ES modules over file://, and the engine cannot be fetched there either. Serve this directory over HTTP, or run prolog-notebook view on the notebook itself. The chapter is readable either way; only the buttons need this.
Cut and control

Where does the fence go?

You have a rule that answers correctly and says everything twice. The obvious fix breaks it completely — and which of the two versions breaks depends on one thing only.

The database

Nothing surprising here: a small family, four facts about who is male, a handful of parent links. Everything further down this page runs against it.

program

The complaint

Ask for every son. Press Run for the first answer, then ; next to walk through the rest — exactly as you would type a semicolon at the interpreter's prompt.

query
?-
the chapter’s saved answers
?- is_son(X).
1. X = edward
2. X = edward
3. X = alfred
4. X = alfred
5. X = george
6. X = george
no more solutions.

edward, then edward again. Nobody has two fathers.

Every son arrives twice. Not a bug in the definition — the definition is correct. Edward genuinely is a son, and Prolog genuinely found two proofs of it: one through father(albert, edward), one through mother(victoria, edward). Prolog reports proofs, not conclusions. Two proofs, two answers.

So stop it after the first proof. once(Goal) proves Goal and then throws away every alternative it found along the way. It is call(Goal), ! in a polite wrapper. Exactly the tool for this — assuming you put it in the right place.

Two places to put it

Here are the only two sensible placements. Version A fences the whole body. Version B fences only the parent lookup. They differ by two parentheses.

program

Sharpen your pencil

Both rules are correct for a single yes/no question: son_a(edward) and son_b(edward) both succeed. But we are about to ask each one to list every son. Write down how many answers you expect from each — then run them.

Reveal the answer (run them first!)

A gives exactly one son. B gives three, each once.

once discards the choice points created inside it — and it can only reach what is inside. In A, male(X) is inside the fence, so the very goal that generates candidates is what gets silenced. The body succeeds once, with edward, and there is nothing left to retry.

In B, male(X) sits outside. Backtracking walks it freely — albert, edward, alfred, george — and for each one the fenced lookup is proved at most once. Duplicates gone, sons intact.

query
?-
the chapter’s saved answers
?- son_a(X).
1. X = edward
no more solutions.
query
?-
the chapter’s saved answers
?- son_b(X).
1. X = edward
2. X = alfred
3. X = george
no more solutions.

The rule underneath

It is tempting to file this under "A is wrong, B is right" and move on. That would be learning the answer instead of the lesson. Look again at why B is safe:

By the time control reaches the fence in B, male(X) has already bound X to a person. The goal inside the fence is therefore asking a yes/no question about edward — it is a test. Throwing away its second proof costs nothing, because we never cared which parent, only whether.

In A, the fence closes around male(X) while X is still unbound. There the enclosed goal is a generator, and once strangles it.

Fence a test, never a generator. And notice that "test" and "generator" are not properties of a predicate. They are properties of a goal at the moment it is called, decided entirely by what is already bound — which is decided entirely by goal order.

Which is why the same predicate can be either. Try it: son_b(george) is a test, son_b(X) is a generator, and it is the same three lines of Prolog both times.

query
?-
the chapter’s saved answers
?- son_b(george).
1. true
no more solutions.

Bullet points

Contents