Prolog Primer

Author
Affiliation

Clayton Cafiero

University of Vermont

Published

2026-05-25

Introduction

“Prolog is a logic programming language associated with artificial intelligence and computational linguistics. Prolog has its roots in first-order logic, a formal logic, and unlike many other programming languages, Prolog is intended primarily as a declarative programming language: the program logic is expressed in terms of relations, represented as facts and rules. A computation is initiated by running a query over these relations.”

—Wikipedia

“A Prolog program is a set of specifications in the first-order predicate calculus describing the objects and relations in a problem domain. The set of specifications is referred to as the database for that problem. The Prolog interpreter responds to questions about this set of specifications. Queries to the database are patterns in the same logical syntax as the database entries. The Prolog interpreter uses pattern-directed search to find whether these queries logically follow from the contents of the database.”

—George Luger, Artificial Intelligence: Structures and Strategies for Complex Problem Solving, 6th ed., 2009

Prolog is different from other languages you’ve seen. It is declarative, rather than imperative. We write rules and facts, and then query the system. We do not write functions or algorithms the way we do in imperative languages.

Prolog takes some getting used to, however, the structure of the language itself is quite simple.

Different authors sometimes use different vocabulary when describing the language elements of Prolog. For example, one author might speak of “predicates” and “arguments,” where a different author might refer to the same things as “relations” between “objects.” Here we will try to adhere to the ISO standard terminology.

Installation

macOS

On macOS, I strongly recommend installation via Homebrew.

brew install swi-prolog

If you don’t have Homebrew, you probably should. See: the Homebrew website.

Linux

On Linux, use your package manager (APT, DNF, Pacman, Zypper, APK, etc.).

Microsoft Windows

For Windows, download pre-built binaries from the SWI-Prolog website.

Terms

Everything in Prolog is constructed from terms. A term is either an atomic term (which includes atoms and numeric literals), a variable, or a compound term (called by some authors a structure).

In Backus-Naur form (BNF):

<atomic term> ::= <atom> | <number>
<term> ::= <variable> | <atomic term> | <compound term>
<term list> ::= <term> | <term> <term list>
<compound term> ::= <functor name>(<term list>)

We say a term is ground if it contains no variables.

The Prolog command-line interface (CLI)

We’re using SWI-Prolog (a widely-used dialect of Prolog). SWI-Prolog provides a convenient interface you can run in your browser, called “Swish”. Depending on how you installed SWI-Prolog, you may have a GUI front-end, or a CLI front-end, or both. I’ll give examples using the command-line interface (CLI).

We can invoke the SWI-Prolog CLI with swipl.

$ swipl
Welcome to SWI-Prolog (threaded, 64 bits, version 10.0.2)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

1 ?-

Now what? Notice the last line is 1 ?-. This is the SWI-Prolog prompt. We can interact with Prolog by issuing commands or queries at the prompt. The leading 1 here is a query number (and can be ignored). SWI-Prolog will increment this automatically.

Files and consulting files

We usually write our Prolog code—in the form of facts and rules—to a file, and then we consult the file to load it into working memory. Like most any other code, Prolog code is stored in plain-text format.

Using the plain-text editor or IDE of your choice, copy and paste the following into a new, empty file, and save the file as “greeting.pl”. By convention, Prolog uses the file extension .pl. (Your operating system may associate this file extension with Perl scripts by default, but you can change the file association if this becomes inconvenient.)

%% A simple collection of facts

greeting(arabic, 'السلام عليكم').
greeting(chinese, '你好').
greeting(english, 'Hello').
greeting(french, 'Bonjour').
greeting(german, 'Guten tag').
greeting(hindi, 'नमस्ते').
greeting(japanese, 'こんにちは。').

Once the file is saved as “greeting.pl”, start the SWI-Prolog CLI, and at the prompt enter the filename within square brackets, and without the .pl extension. Be sure to follow with a full-stop .. SWI-Prolog will look in the current working directory for a file named “greeting.pl” and if found it will load it into memory.

?- [greeting].
true.

If all goes well, SWI-Prolog will report true indicating that the file was found, and loaded into memory without error. To test, issue a simple query:

?- greeting(english, X).
X = 'Hello'.

We’ll go over how this works in subsequent sections. For now, if you get these results, you’re all set.

If you’re in the process of editing file(s) that have already been consulted, you can re-consult all consulted files with make.

Paths and working directory

If you need to navigate to a particular working directory where you’ve saved your Prolog file, you can do this before or after invoking swipl.

Changing working directory before invoking swipl.

$ cd /path/to/your/working/directory
$ swipl
Welcome to SWI-Prolog (threaded, 64 bits, version 10.0.2)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

1 ?-

Changing working directory after invoking swipl

?- cd('/path/to/your/working/directory').
true.

Quitting SWI-Prolog

To quit, use halt..

?- halt.

Comments

In Prolog, inline comments start with % and continue to the end of the line. Document level comments may appear within these familiar delimiters: /* <comment> */.

Examples:

/*
  I am a Prolog file used for some wonderful purpose.

  @author Egbert Porcupine
*/

% This is an inline comment

Atoms and variables

Prolog is case-sensitive, but not in the way you might think. Lower-case denotes an atom or the name of a functor. Variables start with upper-case letters.

hot(sun).  % hot is the name of the functor. sun is an atom.
hot(X).    % hot is, again, the name of the functor. X is a variable

Like Python and many other languages (Rust, Go, OCaml, Haskell, Erlang, Lua, etc.), we can use the single underscore (_) as an identifier for a variable whose value we don’t need, don’t care about, and won’t read. Unlike other languages, where _ is used as an anonymous variable by convention, in Prolog, _ is the anonymous variable.

Clauses

A Prolog program consists of one or more clauses (typically more than one). Clauses in a program come in two flavors: facts and rules. Rules have a head and a body. Facts are clauses without a body.

The general form of a clause is

head :- body.

We may read this as: if body is true, then the head is true. If the head is always true, without condition, we call this a fact, and we may omit the body.

The clause is the only language construct in Prolog. That’s it.

In BNF:

<clause> ::= <compound term> :- <compound terms> | <compound term>
<compound terms> ::= <compound term> | <compound term>, <compound terms>

Facts

Perhaps the simplest thing we can do in Prolog is to write facts about objects.

color(sky, blue).

A fact is a clause without a body—there is no condition to be satisfied. It is a bare assertion (just what we want for a fact!). Facts must end with a period.

Note that the order of objects is arbitrary, that is, we could just as easily have written

color(blue, sky).

However, these are two different facts!

It is usually helpful to have a reading of each type of fact, to help us remain consistent in usage. We might read color(sky, blue) as “the color of the sky is blue,” making clear that color(blue, sky) means something different.

Another example:

enjoys(mary, brussels_sprouts).

This asserts that Mary enjoys brussels sprouts. The order of arguments is part of the meaning—we read the functor name and arguments consistently.

We refer to the part outside the parentheses as the functor name, the objects within as the arguments, and the number of arguments as the arity.

Some other facts (note that % indicates a single-line comment):

hot(sun).  % the sun is hot
play(magnus, yifan, chess).  % magnus and yifan play chess
gives(sarah, waldo, flower).  % sarah gives a flower to waldo

Rules

Rules in Prolog consist of a head and a body, separated by the symbol :-. We read the :- as “if,” but it is the head which is true as a consequence of the body being true.

pumpkin(X) :-
    winter_squash(X),
    big(X),
    orange(X).

Remember that if we use the comma between goals, it means logical AND, so the reading we give this in Prolog is

X is a pumpkin IF
    X is a winter squash
    AND X is big
    AND X is orange

This reverses the order we’re used to in imperative languages, which have an IF-THEN structure. This is roughly equivalent to

IF
    X is a winter squash
    AND X is big
    AND X is orange
THEN
    X is a pumpkin

We refer to the parts of the body as goals. The comma between goals represents the logical connective AND. Rules, like facts, end with a period.

So why are Prolog rules structured the way they are? The answer is rooted in the use of Horn clauses (we’ll get to these shortly).

Ordering

In Prolog, it’s best practice to keep your predicates alphabetized in the knowledge-base, and to group all predicates with the same identifier and arity together. This significantly speeds up debugging and makes large databases easier to traverse.

Connectives

In Prolog, the comma and semicolon have very different meanings than they do in other programming languages. We’ve seen that we use the comma to separate arguments. However, when building compound rules or queries—rules or queries that have more than one goal—the comma functions as logical AND. The semicolon functions as logical OR. Negation is written \+ (go figure).

X, Y   % X AND Y
X; Y   % X OR Y
\+X    % NOT X

Horn clauses

In logic, a clause is a disjunction of literals,

L_1 \lor L_2 \lor \ldots \lor L_n

where each L_k is a literal.

A Horn clause is a disjunction of literals with at most one positive literal. A definite Horn clause is a Horn clause with exactly one positive literal. A positive literal is just an atom, e.g., x, whereas a negative literal is the negation of an atom, e.g., \neg x.

The general form of a definite Horn clause is

H \lor \neg G_1 \lor \neg G_2 \lor \ldots \lor \neg G_n

In this form, we call H the head of the clause and the rest is the body of the clause.

Using De Morgan’s laws we may rewrite this as

H \lor \neg (G_1 \land G_2 \land \ldots \land G_n)

Recall that A \lor \neg B \equiv A \leftarrow B.

This lets us rewrite the general form as

H \leftarrow (G_1 \land G_2 \land \ldots \land G_n)

In Prolog, :- signifies \leftarrow and , signifies \land. So we may express this in Prolog as a rule:

H :- G1, G2, ..., Gn.

To complete the picture, we need only consider facts and queries. A fact is just a head without a body—a bare assertion. A query is the opposite—a body without a head!

H :- G1, G2, ..., Gn.  % a rule
H.                     % a fact
  ?- G1, G2, ..., Gn.  % a query

It turns out that all computations can be expressed as Horn clauses. This means that Prolog, which is constructed exclusively from Horn clauses, is Turing complete!

Variables and scope

As noted, variables are written with names starting with a capital letter, e.g., X, Weight, or Volume. Variables are declared by using them—unlike C or Java there is no explicit declaration.

The scope of a variable is limited to the rule or clause in which it appears. Variables have no life outside this scope.

Example (where ?- is the Prolog query prompt):

?- X = 2, write(X).
2
X = 2.

This instantiates X with a value of 2 and prints it to the console. When the clause terminates (at the period), X goes out of scope.

If we continue:

?- write(X).  % write is a Prolog built-in
_31860
true.

This is a new X, which has no value. In Prolog we refer to this as an uninstantiated variable.

Note that the scope of variables is different from the scope of terms. Terms are always in scope.

When we write a rule, we use variables, for example:

is_parent_of(X, Y) :-
    child_of(Y, X).

Queries

We don’t run a Prolog program in the conventional sense. Instead we consult a file (or files) containing facts and rules, and then we issue queries.

Let’s say we had these rules:

pet(X) :-
    mammal(X),     % yeah, we're ignoring parakeets and turtles
    \+human(X),    % humans can't be pets
    friendly(X),   % otherwise, why bother?
    \+prickly(X).  % alas, porcupines don't make good pets

mammal(X) :-
    human(X);
    dog(X);
    cat(X);
    bear(X);
    bat(X);
    catamount(X);
    porcupine(X).

friendly(X) :-
    human(X);
    dog(X);
    cat(X);
    porcupine(X).  % really, they are!

prickly(X) :-
    porcupine(X).

human(jim).
dog(fido).
cat(esmerelda).
bat(victor).
catamount(rally).    % with some license
porcupine(egbert).

% If we have any unnamed critters, we need this:
:- dynamic bear/1.
% A dynamic declaration tells SWI-Prolog the predicate exists but has no clauses (so it simply fails if no match).

Then, at the swipl console, we could write some queries.

?- pet(jim).
false.

?- pet(fido).
true.

?- pet(rally).
false.

?- pet(egbert).
false.

In each of the examples above, we’re asking Prolog if these are pets. Now let’s see what happens if we use a variable rather than an atom in our query.

?- pet(X).
X = fido ;
X = esmerelda ;
false.

What happened here? The query pet(X) is asking “What X is a pet?” Prolog works to find how this query can be satisfied. At first, it finds that fido is a pet. Then it pauses, offering the user the option to continue to see if there are other possibilities, or terminating the query. In the example above, the user has opted to continue by typing a semicolon which signifies logical OR. Prolog continues and finds that esmerelda also is a pet. When the user asks for more, again, by typing a semicolon, Prolog ends with false.. This indicates that there are no more pets that Prolog knows about.

Notice that in the code, there’s nothing that explicitly tags a given entity as a pet. Instead, we give some facts and rules, and Prolog infers from these what is and is not a pet. The crucial rule is

pet(X) :-
    mammal(X),     % yeah, we're ignoring parakeets and turtles
    \+human(X),    % humans can't be pets
    friendly(X),   % otherwise, why bother?
    \+prickly(X).  % alas, porcupines don't make good pets

This tells Prolog that a pet is a friendly, non-human mammal, that’s not prickly.

Predicates, functors, and arity

In Prolog, we have functors and predicates. A functor is a syntactic element used to construct compound terms. A predicate is a functor that’s used to define a relation that can be called as a goal.

% functor
point(3, 4)          % a data structure representing a point in coordinate space

% predicate
likes(mary, pizza)   % a relation that can be used as a fact or part of a query

Prolog provides a great many built-in functors and predicates. These have an identifier and a number of arguments (as shown above). Prolog supports arity overloading, so functors and predicates are distinguished by their number of arguments. We refer to functors and predicates by indicating their arity.

  • point/2 is a functor with the identifier point which takes two arguments. We could distinguish this from a different functor which takes three arguments to construct a point in three-dimensional space, point/3.
  • likes/2 is a predicate with two arguments.

Unification

In Prolog, = is not assignment, it is unification. Given two variables, X and Y,

X = Y.

unifies X and Y. That is, after unification the two variable names X and Y reference the same variable. This means that the following are equivalent:

X = Y.
Y = X.

Unification and backtracking are essential aspects of how Prolog works.

In the context of our earlier query pet(X), Prolog seeks to find what can be unified with X to satisfy the query.

We’ll see more about backtracking later.

Arithmetic evaluation and unification

Because unification is not the same as assignment, and because we often wish to evaluate some arithmetic expression and unify the result with some variable, Prolog provides is/2. is/2 evaluates the expression on the right-hand side and unifies it with the left-hand side.

?- X is 1 + 2.
X = 3.

Notice that this is very different from the result we get if using =/2.

?- X = 1 + 2.
X = 1+2.

In the first case, with is/2, the expression 1 + 2 is evaluated first. In the second, with =/2, the expression is not evaluated, and X is unified with the expression 1 + 2.

A final example should drive the point home:

?- X = 1+2, Y is X.
X = 1+2,
Y = 3.

Comparison

Prolog supports comparison with (mostly) familiar operators, although comparison operators differ depending on whether we’re performing arithmetic comparison, in which operands must evaluate to numbers, and term comparison, in which we compare terms with respect to standard ordering.

Arithmetic comparison operators

X > Y        % true if X is greater than Y
X < Y        % true if X is less than Y
X >= Y       % true if X is greater than or equal to Y
X =< Y       % true if X is less than or equal to Y
X =\= Y      % true if X does not equal Y
X =:= Y      % true if X equals Y

Term comparison operators

X == Y       % true if X and Y are identical
X \== Y      % true if X and Y are not identical
X @< Y       % true if X precedes Y in standard order
X @> Y       % true if X follows Y in standard order
X @=< Y      % true if X precedes Y in standard order or X == Y
X @>= Y      % true if X follows Y in standard order or X == Y

There’s an alternative syntax for testing equivalence using ==/2 and \==/2.

==(X, Y)     % true if X equals Y
\==(X, Y)    % true if X does not equal Y

Lists

Prolog supports lists. The response to a query might be a list. We often construct lists to satisfy some query. Prolog lists are written with square brackets.

The empty list is as you’d expect: [].

Lists do not work the way you’d expect them to in imperative languages. We construct and deconstruct lists with a common syntax.

[H | T]   % head | tail, or if you prefer
[Head | Tail]

We refer to the first element of a list as the head (H in the example above), and we refer to the rest of the list as the tail (T in the example above). Notice that the list appears within square brackets, and the head and tail are separated by the vertical bar, |. Whether this constructs a list or deconstructs a list depends on context and usage.

Deconstruction

This deconstructs the list on the right-hand side into head and tail.

?- [H | T] = ['a', 'b', 'c', 'd'].
H = a,
T = [b, c, d].

Construction

Here’s an example of constructing a list.

?- H = 'a', T = ['b', 'c'], List = [H | T].
H = a,
T = [b, c],
List = [a, b, c].

Notice that the only difference between deconstruction and construction is that [H | T] is on the right-hand side of = for construction, and [H | T] is on the left-hand side for deconstruction.

Recursive construction

Here’s an example of constructing a list recursively. We’ll construct lists of all the natural numbers from some positive integer N down to 1.

% Recursive list construction

range(1, [1]).

range(N, [N | T]) :-
    N > 1,
    M is N - 1,
    range(M, T).

Here’s a quick demonstration, assuming this file is saved as “make_list.pl”. (If you wish you can save the snippet of code above to a .pl file and consult the file.)

?- [make_list].
true

?- range(1, List).
List = [1]

This hits the base case and returns the result for the base case, the list containing the integer 1. The variable List has no value until Prolog unifies it with [1]. We call a variable without a value free or unbound.

Notice that Prolog’s response is not followed by a full-stop. This is called a decision point, and it allows a user to see if there are other ways of satisfying a given query. Here, we know there aren’t, so we can go ahead and type the full-stop to terminate the query.

?- range(1, X).
X = [1] .

Now let’s try with a different positive integer, say, five.

?- range(5, X).
X = [5, 4, 3, 2, 1] .  % again, the user supplies . to terminate the query

There’s a lot going on here, so let’s unpack it carefully.

range/2 is user-defined—this is not calling some built-in. The definition is in two parts: a base case and a recursive case.

The base case is given by

range(1, [1]).

When we issue the query range(1, List)., Prolog finds a match in the base case—the first argument is 1. To satisfy the query, the variable List is unified with [1], and we get the result:

?- range(1, List).
List = [1] .

Things get more interesting when we issue the query range(5, List).. Now, clearly this does not match the base case, so Prolog moves on to the recursive case.

range(N, [N | T]) :-
    N > 1,
    M is N - 1,
    range(M, T).

Initially, the variable N is unified with 5, but the second argument in the query was the variable List. Within the body of the rule, there’s a check N > 1. This fails if N is less than or equal to one, providing a crucial guard. If this evaluates to true then Prolog moves on to the next line M is N - 1 (remember , in Prolog is logical AND). This evaluates N - 1 and unifies this with M. Then, a recursive call is made. The recursive call is range(M, T), which with M = 4 becomes range(4, T). Notice that T is still unbound—it will only get a value once the recursion bottoms out.

This process repeats: Prolog tries to satisfy range(4, T), finds the recursive case again, computes M is 4 - 1, and calls range(3, T). Then range(2, T), and finally range(1, T). At that point, the first argument is 1, which matches the base case: range(1, [1]). So T is unified with [1], and the deepest recursive call is satisfied.

Now the recursion unwinds. Each pending call was waiting on the result of the call below it, and each has the form [N | T] in the head. As we return back up the call stack, the list gets built up one element at a time:

  • range(1, T) returns T = [1]
  • range(2, [2 | T]) with T = [1] gives [2, 1]
  • range(3, [3 | T]) with T = [2, 1] gives [3, 2, 1]
  • range(4, [4 | T]) with T = [3, 2, 1] gives [4, 3, 2, 1]
  • range(5, [5 | T]) with T = [4, 3, 2, 1] gives [5, 4, 3, 2, 1]

The final result is unified with List in the original query, giving us List = [5, 4, 3, 2, 1].

The key insight is that the list is not built on the way down into the recursion—it is built on the way back up. Each recursive call contributes one element to the front of the list via the [N | T] constructor in the head of the rule, but that element can only be prepended once T has been determined by the call below it.

This pattern—a base case that returns a seed value, and a recursive case that prepends to a tail returned by a deeper call—is one of the most common idioms in Prolog list processing.

Notice that Prolog determined which rules to apply (based on pattern matching). There’s no written flow of control as there would be in an imperative language.

Prolog has some built-ins that come in handy when it comes to lists. The predicate member/2 allows you to check for membership in a list. For example,

?- member(X, SomeList).

yields true if X is a member of SomeList and false otherwise.

Recursion

You might ask, “Why don’t we just use a loop?” The answer may surprise you: Prolog does not have loops! The only iteration mechanism Prolog has is recursion.

We saw in the example above, how Prolog constructs a list recursively. Like most programming languages that support recursion, Prolog uses a call stack to keep track of the execution of predicates, including recursive calls. Here’s how it works in Prolog:

Call stack during recursion: When Prolog encounters a predicate call, like range(3, List), it pushes that call onto the call stack. For each recursive call, a new stack frame is pushed onto the call stack. The stack frame contains information about the current state of the execution, including variables, the current goal being processed, and how to resume after the call completes.

Unwinding the call stack: When the base case of a recursive predicate is reached, Prolog starts to unwind the call stack. As it returns from each recursive call, it pops a frame off the stack, resolves the variables for that call, and continues with the remaining logic in the predicate.

Backtracking

Prolog’s call stack is also used to manage backtracking. Backtracking is one of the most important aspects of how Prolog finds satisfaction of queries. If a predicate call fails, Prolog pops the stack frame and goes back to the previous decision point, trying alternative solutions.

We must consider backtracking when ordering clauses within the body of a rule.

In Prolog we can have multiple clauses for the same predicate. Let’s revisit the earlier example of greetings.

greeting(arabic, 'السلام عليكم').
greeting(chinese, '你好').
greeting(english, 'Hello').
greeting(french, 'Bonjour').
greeting(german, 'Guten tag').
greeting(hindi, 'नमस्ते').
greeting(japanese, 'こんにちは。').

Let’s say we issue the query

?- greeting(japanese, X).
X = 'こんにちは。'

To satisfy this query, Prolog searches the database from top to bottom, trying each greeting clause in turn. It first tries greeting(arabic, 'السلام عليكم'). Prolog attempts to unify the first argument arabic with japanese. This fails, so Prolog backtracks and tries the next clause: greeting(chinese, '你好'). Again, chinese does not unify with japanese, so Prolog backtracks again. This continues until Prolog reaches greeting(japanese, 'こんにちは。'). Here, japanese unifies with japanese, and X is unified with 'こんにちは。'. The query succeeds with no remaining choice points, since there are no further greeting clauses.

We can make backtracking more visible by using a variable for the first argument instead:

?- greeting(L, _).
L = arabic ;
L = chinese ;
L = english ;
L = french ;
L = german ;
L = hindi ;
L = japanese.

Each time the user types ;, Prolog backtracks to try the next greeting clause. When there are no more clauses to try, the query terminates.

Prolog uses backtracking to implement what other languages do with conditionals or dispatch. Notice that there are no explicit control-of-flow structures at work here. Prolog does this all on its own.

Collecting query results with findall/3

findall/3 is a built-in predicate for collecting multiple solutions into a list, rather than presenting them one at a time interactively.

findall/3 has the form findall(Template, Goal, List). It finds all solutions to Goal, collects the value of Template for each solution, and unifies the resulting list with List. If there are no solutions, List is unified with the empty list [].

Using our greeting example:

?- findall(L, greeting(L, _), Languages).
Languages = [arabic, chinese, english, french, german, hindi, japanese].

Printing to console

write/1 writes a term to the console. nl/0 writes a newline. They are often used together:

?- write('Hello, World!'), nl.
Hello, World!
true.

A convenient shorthand is writeln/1, which writes a term followed by a newline in a single call:

?- writeln('Hello, World!').
Hello, World!
true.

These are useful for printing intermediate values when debugging or when you want to display results from within a rule rather than relying on Prolog’s built-in answer reporting. For example:

print_greeting(Language) :-
    greeting(Language, G),
    write(Language), write(': '), writeln(G).

Now let’s try it out.

?- print_greeting(french).
french: Bonjour
true.

Tracing, spying, and debugging

trace/0 puts SWI-Prolog into trace mode, which prints each step of execution as Prolog works to satisfy a query. This is invaluable when a program isn’t behaving as expected.

?- trace.
true.

[trace] ?- greeting(french, X).
    Call: (10) greeting(french, _G123) ? creep
    Exit: (10) greeting(french, 'Bonjour') ? creep
X = 'Bonjour'.

At each step, Prolog prints a port label and the current goal. The four ports you’ll see most often are:

  • Call — Prolog is about to attempt the goal.
  • Exit — the goal succeeded.
  • Fail — the goal failed; Prolog will backtrack.
  • Redo — Prolog has backtracked and is retrying the goal.

When prompted with “?”, pressing “Enter” (or typing “c”) moves to the next step (hence “creep”).

Other options include “s” to skip into a sub-goal, “a” to abort, and “h” for a full list of commands.

Turn tracing off with notrace/0.

[trace] ?- notrace.
true.

A useful complement is spy/1, which sets a spy point on a specific predicate so that tracing only activates when that predicate is called, rather than for every step. This is handy in larger programs where full tracing produces too much output:

?- spy(greeting/2).

Remove a spy point with nospy/1, or clear all spy points with nospyall/0.

debug/0 and nodebug/0 toggle debug mode, which is a lower-noise alternative to full tracing. In debug mode, Prolog only pauses at spy points you have set with spy/1, and it runs normally everywhere else.

The typical workflow is:

  1. Set a spy point on the predicate you’re investigating.
  2. Enable debug mode.
  3. Issue your query; Prolog runs silently until it hits the spy point, then shows trace output.
?- spy(pet/1).
% Spy point set on pet/1
true.

?- debug.
true.

[debug] ?- pet(X).
Call: (10) pet(fido) ? creep
Exit: (10) pet(fido) ? creep
X = fido ;
Redo: (10) pet(_G123) ? creep
Call: (10) pet(esmerelda) ? creep
Exit: (10) pet(esmerelda) ? creep
X = esmerelda ;
...

Turn debug mode off with nodebug/0.

[debug] ?- nodebug.
true.

The distinction between trace and debug is one of granularity. Use trace when you want to step through every single goal and see the full execution path. Use debug with spy points when you already have a hypothesis about where things go wrong and want to focus only on a specific predicate without wading through unrelated output.

Documentation and help

The official SWI-Prolog documentation is available at https://www.swi-prolog.org/.

For help on Prolog language features you can use apropos/1 to print portions of the built-in Prolog manual. For example,

?- apropos(member).
% LIB member/2                    True if Elem is a member of List.
% SWI memberchk/2                 True when Elem is an element of List.
% LIB max_member/2                True when Max is the largest member in the standard order ...
% LIB min_member/2                True when Min is the smallest member in the standard orde ...
% LIB max_member/3                True when Max is the largest member according to Pred, wh ...
% LIB min_member/3                True when Min is the smallest member according to Pred, w ...
% LIB hub_member/2                True when Id is a member of the hub HubName.
% LIB rdf_member/2                True when Member is a member of RDFList
% LIB rdfs_member/2               True if rdf(Container, P, Elem) is true and P is a contai ...
% LIB rdfs_member/2               Test or generate the members of Set.
% LIB fdset_member/2              The integer Elt is a member of the FD set Set.
% LIB ord_memberchk/2             True if Element is a member of OrdSet, compared using ==.
% LIB random_member/2             X is a random member of List.
% LIB directory_member/3          True when Member is a path inside Directory.
% SWI trie_gen/2                  True when Key is a member of Trie.
% LIB rdfs_container_membership_property/1 True when Property is a container membership pro ...
% LIB rdfs_container_membership_property/2 True when Property is the Nth container membersh ...
% SEC 'clpfd-membership'          Membership constraints
% LIB gen_nb_set/2                Generate all members of Set on backtracking in the standa ...
% SWI apply/2                     Append the members of List to the arguments of Goal and c ...
% Showing 20 of 131 matches
%
% Use ?- apropos(Type:Query) or multiple words in Query
% to restrict your search.  For example:
%
%   ?- apropos(iso:open).
%   ?- apropos('open file').
true.

You can also use the Prolog built-in help/1. For example,

Availability: :- use_module(library(lists)). (can be autoloaded)

member(?Elem, ?List)
  True if Elem is a member of List. The SWI-Prolog definition differs from the classical
  one. Our definition avoids unpacking each list element twice and provides determinism
  on the last element. E.g. this is deterministic:

    member(X, [One]).

    author
        Gertjan van Noord

When you’re done with a help topic, type q to quit.

If you’re looking for a good external resource on Prolog visit: The Power of Prolog.

ISO definitions

  • atom: A basic object, denoted by an identifier
  • atomic term: An atom or a number.
  • body: A goal, distinguished by its context as part of a rule.
  • clause: A fact or a rule. A rule has two parts: a head, and a body. A fact is a clause with only a head.
  • compound term: A functor of arity N, N positive, together with a sequence of N arguments
  • database: The set of user-defined procedures which currently exist during execution.
  • functor: An identifier together with an arity.
  • functor name: The identifier of a functor.
  • goal: A predication which is to be executed
  • ground: An atomic term or a compound term whose arguments are all ground. A term is ground with respect to a substitution if application of the substitution yields a ground term.
  • head (of a rule): A predication, distinguished by its context.
  • identifier: A basic unstructured object used to denote an atom, functor name or predicate name.
  • instantiated: A variable is instantiated with respect to a substitution if application of the substitution yields an atomic term or a compound term. A term is instantiated if any of its variables are instantiated.
  • predicate: An identifier together with an arity.
  • predication: A predicate with arity N and a sequence of N arguments.
  • rule: A clause whose body is not the goal true. During execution, if the body is true for some substitution, then the head is also true for that substitution. A rule is represented in Prolog text by a term whose principal functor is (:—) / 2 where the first argument is converted to the head, and the second argument is converted to the body.
  • uninstantiated: A variable is uninstantiated when it is not instantiated.
  • variable: An object which may become instantiated to a term during execution,

References

  1. Bratko, Ivan. 2012. Prolog Programming for Artificial Intelligence, 4th ed., Pearson.
  2. Clocksin, William F., and Mellish, Christopher S. 2003. Programming in Prolog: Using the ISO Standard, 5th ed., Springer-Verlag.
  3. ISO/IEC 13211-1:1995 Information technology – Programming languages – Prolog – Part 1: General core.
  4. Matuszek, David. 2012. A Concise Guide to Prolog.
  5. Triska, Markus. 2022. The Power of Prolog.. (Highly recommended.)
  6. About Backus-Naur form on Wikipedia.

Copyright © 2023–2026 Clayton Cafiero

No generative AI was used in producing drafts of this material. This was written the old-fashioned way. AI was used to rewrite existing pseudocode in LaTeX to produce standalone *.tex files for rendering, and for revisions toward satisfying WCAG 2.1 AA-level accessibility standards as required by UVM policy. It may also have been used to proofread selected human-written prose. Claude 2.1.150 with model Sonnet 4.6. Revisions, if any, were performed by the author. AI was not used in generating any code whatsoever. All code samples and starter code are by the author only.