Beginner

Variables and Types in Prolog

Explore atoms, numbers, variables, and terms in Prolog, where unification replaces assignment and types describe the shape of logical data

Prolog’s data model is radically different from mainstream languages. There are no typed declarations, no assignment statements, and no primitive/object divide. Instead, everything is a term, and a “variable” is not a storage location but a placeholder that the Prolog engine tries to bind through unification.

As a logic programming language, Prolog is dynamically typed and untyped at the term level. A variable can stand for any term — an atom, a number, a list, a compound structure, or even another variable — and its “type” is determined by the shape of whatever it eventually unifies with. You never declare types; you inspect them with built-in predicates when you need to.

In this tutorial you’ll learn the five term categories that make up every Prolog program — atoms, numbers, variables, compound terms, and lists — and you’ll see why X = 3 in Prolog does not mean “store 3 in X” but rather “prove that X and 3 can be made identical.”

The Five Kinds of Terms

Every value in Prolog is a term, and every term belongs to exactly one of these categories:

Term KindExampleStarts With
Atomhello, 'Alice Smith', []lowercase letter or quoted
Number42, -17, 3.14digit or minus sign
VariableX, Result, _Tmp, _uppercase letter or underscore
Compound termfoo(bar, 3), point(1, 2)functor + arguments
List[1, 2, 3], `[HT]`

The capitalization rule is load-bearing: alice is an atom (a constant), while Alice is a variable (a placeholder). Getting this wrong is the most common mistake new Prolog programmers make.

Unification Is Not Assignment

In imperative languages, x = 3 overwrites whatever was in x. In Prolog, X = 3 asks the engine: “Can X and 3 be made identical? If so, bind X to make it so.” This operation is called unification.

A variable can be bound only once within a proof. Once X is bound to 3, trying to unify it with 4 fails:

1
2
?- X = 3, X = 4.
false.

This single-assignment property comes directly from Prolog’s mathematical foundation — a logical variable represents one specific (if unknown) value within a proof, not a memory cell that changes over time.

Unification also works structurally. Two compound terms unify if and only if their functors match and their arguments unify pairwise:

1
2
3
?- point(X, 4) = point(3, Y).
X = 3,
Y = 4.

A Complete Example

Create a file named variables.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
% Variables and Types in Prolog
% Demonstrates atoms, numbers, unification, compound terms, and type inspection.

:- initialization(main).

main :-
    % --- Atoms: symbolic constants ---
    Greeting = hello,
    Name     = 'Alice Smith',
    format('Atom:        ~w~n', [Greeting]),
    format('Quoted atom: ~w~n', [Name]),

    % --- Numbers: integers and floats ---
    % 'is' evaluates an arithmetic expression on the right.
    Age      is 30,
    Pi       is 3.14159,
    Sum      is 10 + 5,
    Product  is 6 * 7,
    format('Integer:     ~w~n', [Age]),
    format('Float:       ~w~n', [Pi]),
    format('Sum:         ~w~n', [Sum]),
    format('Product:     ~w~n', [Product]),

    % --- Unification: the real "assignment" ---
    % Structural unification binds X and Y by matching shapes.
    point(X, Y) = point(3, 4),
    format('Point:       X = ~w, Y = ~w~n', [X, Y]),

    % --- Compound terms and lists ---
    Person   = person('Bob', 42, engineer),
    Numbers  = [1, 2, 3, 4, 5],
    [Head|Tail] = Numbers,
    format('Person:      ~w~n', [Person]),
    format('List:        ~w~n', [Numbers]),
    format('Head: ~w  Tail: ~w~n', [Head, Tail]),

    % --- Type checking ---
    nl, write('Type checks:'), nl,
    check_type(hello),
    check_type(42),
    check_type(3.14),
    check_type([1, 2, 3]),
    check_type(foo(bar, baz)),

    halt.

% Classify a term by asking Prolog's type predicates in order.
% The order matters: is_list/1 must come before compound/1 because
% lists are themselves compound terms built from the '.'/2 functor.
check_type(Term) :-
    (   atom(Term)     -> Type = atom
    ;   integer(Term)  -> Type = integer
    ;   float(Term)    -> Type = float
    ;   is_list(Term)  -> Type = list
    ;   compound(Term) -> Type = compound
    ;   Type = unknown
    ),
    format('  ~w -> ~w~n', [Term, Type]).

What’s Happening

  • = performs unification. It never “evaluates” — it only matches shapes and binds variables.
  • is is the only arithmetic operator. X is 10 + 5 evaluates the right-hand side and unifies X with the result. Writing X = 10 + 5 would bind X to the unevaluated term +(10, 5).
  • point(X, Y) = point(3, 4) destructures in one step — there is no separate “pattern match” syntax because unification is pattern matching.
  • [Head|Tail] = Numbers splits a list using the cons pattern [H|T]. The head is the first element, the tail is the rest.
  • atom/1, integer/1, float/1, is_list/1, and compound/1 are type-inspection predicates. They succeed or fail rather than returning a type name.

Running with Docker

1
2
3
4
5
# Pull the SWI-Prolog image
docker pull swipl:stable

# Run the example
docker run --rm -v $(pwd):/app -w /app swipl:stable swipl -q variables.pl

The -q flag silences SWI-Prolog’s startup banner so only your program’s output appears.

Expected Output

Atom:        hello
Quoted atom: Alice Smith
Integer:     30
Float:       3.14159
Sum:         15
Product:     42
Point:       X = 3, Y = 4
Person:      person(Bob,42,engineer)
List:        [1,2,3,4,5]
Head: 1  Tail: [2,3,4,5]

Type checks:
  hello -> atom
  42 -> integer
  3.14 -> float
  [1,2,3] -> list
  foo(bar,baz) -> compound

Atoms vs. Strings

Prolog has three text-like representations, and the distinction matters:

  • Unquoted atomhello, foo_bar. Starts with lowercase. Fast, interned symbol.
  • Quoted atom'Alice Smith', 'hello world'. Needed when the text contains spaces, punctuation, or starts with uppercase.
  • String"hello". In SWI-Prolog 7+ these are a dedicated string type; in classical ISO Prolog they are lists of character codes. Stick with atoms unless you specifically need string operations.
1
2
3
4
5
6
7
8
?- atom(hello).
true.

?- atom('Alice Smith').
true.

?- atom("hello").
false.        % a string is not an atom

Unbound Variables and the Anonymous _

A variable that has not yet been bound is called unbound or fresh. You can test for this with var/1:

1
2
3
4
5
?- var(X).
true.

?- X = 3, var(X).
false.

The underscore _ is the anonymous variable — a fresh, unnamed variable. Every occurrence of _ is a different variable, which makes it useful for ignoring parts of a term:

1
2
3
% Extract the first element; ignore the rest.
?- [First | _] = [a, b, c, d].
First = a.

Names starting with underscore (_Temp, _Ignored) are also treated as “don’t-care” variables — the compiler won’t warn about them being singleton.

Key Concepts

  • Everything is a term. Atoms, numbers, variables, and compound terms are the only things Prolog manipulates — there is no class/primitive divide.
  • Case is semantic, not stylistic. Lowercase names are atoms; uppercase (or underscore-prefixed) names are variables.
  • Unification replaces assignment. = makes two terms identical by binding variables; it never mutates.
  • Single-assignment within a proof. Once a variable is bound, it stays bound until Prolog backtracks past that binding.
  • is evaluates arithmetic; = does not. X is 2 + 3 binds X to 5; X = 2 + 3 binds X to the term +(2, 3).
  • Types are inspected, not declared. Predicates like atom/1, integer/1, compound/1, and is_list/1 ask “what shape does this term have?” at runtime.
  • Lists are compound terms in disguise. [1, 2, 3] is sugar for '.'(1, '.'(2, '.'(3, []))), which is why compound([1,2,3]) succeeds.
  • The anonymous _ ignores values. Use it whenever you need to unify a position without caring what ends up there.

Running Today

All examples can be run using Docker:

docker pull swipl:stable
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining