Beginner

Operators in Prolog

Learn how operators work in Prolog: arithmetic evaluation with is/2, comparison, unification, logical connectives, and operator precedence with Docker-ready examples

In most languages an operator like + quietly computes something. In Prolog, that assumption will trip you up. Prolog is a logic language, so 1 + 2 is not the number 3 — it is a term, a tree with the functor + and the two arguments 1 and 2. Nothing is calculated until you explicitly ask for it.

This single idea explains almost every surprise newcomers hit with Prolog operators. There are three different “equals” (=, =:=, and ==), arithmetic only happens inside is/2, and operators like , and ; are really control connectives that drive Prolog’s search. Operators in Prolog are just syntactic sugar for ordinary terms, which is why you can even define your own.

This tutorial covers the operators you will use constantly: arithmetic, comparison, unification, and the logical connectives — plus how precedence turns a flat line of symbols into a structured term.

Arithmetic Operators and is/2

Arithmetic in Prolog is evaluated, not assumed. The is/2 operator takes an arithmetic expression on its right and unifies the resulting number with the variable on its left. Writing X = 2 + 3 would bind X to the unevaluated term 2+3; writing X is 2 + 3 binds X to 5.

Create a file named arithmetic.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
:- initialization(main).

main :-
    Sum    is 8 + 5,
    Diff   is 8 - 5,
    Prod   is 8 * 5,
    IntDiv is 17 // 5,      % integer division (truncates)
    Rem    is 17 mod 5,     % remainder
    Pow    is 2 ^ 8,        % integer power
    Quot   is 10.0 / 4,     % float division
    Abs    is abs(-9),      % arithmetic functions, not operators
    Max    is max(3, 7),
    Sq     is sqrt(16),
    format('8 + 5     = ~w~n', [Sum]),
    format('8 - 5     = ~w~n', [Diff]),
    format('8 * 5     = ~w~n', [Prod]),
    format('17 // 5   = ~w~n', [IntDiv]),
    format('17 mod 5  = ~w~n', [Rem]),
    format('2 ^ 8     = ~w~n', [Pow]),
    format('10.0 / 4  = ~w~n', [Quot]),
    format('abs(-9)   = ~w~n', [Abs]),
    format('max(3, 7) = ~w~n', [Max]),
    format('sqrt(16)  = ~w~n', [Sq]),
    halt.

Note that ^ keeps the result an integer (256), while sqrt/1 always returns a float (4.0). Functions like abs, max, min, gcd, and sqrt are not operators at all — they are functors that is/2 knows how to evaluate.

Comparison Operators

Once values are computed, you compare them. The arithmetic comparison operators evaluate both sides as expressions before comparing, so 3 + 4 =:= 7 succeeds. Each comparison is a goal that either succeeds or fails — there is no boolean value to store.

OperatorMeaning
=:=arithmetic values equal
=\=arithmetic values not equal
<, >less / greater than
=<, >=less-or-equal / greater-or-equal (note =<, not <=)

Create a file named comparison.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
:- initialization(main).

show(Label, Goal) :-
    ( call(Goal) -> Result = true ; Result = false ),
    format('~w  -->  ~w~n', [Label, Result]).

main :-
    show('3 + 4 =:= 7', (3 + 4 =:= 7)),
    show('2 * 5 =\\= 11', (2 * 5 =\= 11)),
    show('10 > 9', (10 > 9)),
    show('5 < 5', (5 < 5)),
    show('5 =< 5', (5 =< 5)),
    show('8 >= 10', (8 >= 10)),
    halt.

The helper show/2 uses the if-then-else operator ( Cond -> Then ; Else ) to turn a goal’s success or failure into a printable word. Watch the spelling: Prolog writes less-or-equal as =<, not <=.

Unification vs. Equality

This is the operator distinction that matters most in Prolog. There are three things that look like equality but do very different jobs:

  • = (unification) — tries to make two terms identical by binding variables. It does not evaluate arithmetic.
  • =:= (arithmetic equality) — evaluates both sides as numbers and compares the values.
  • == (term identity) — tests whether two terms are already structurally identical, binding nothing.

Create a file named unification.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
:- initialization(main).

main :-
    % = is unification, not assignment: it binds variables
    Point = point(3, 4),
    format('Unified:  Point = ~w~n', [Point]),

    % Unification can also destructure a term
    Point = point(Px, Py),
    format('Px = ~w, Py = ~w~n', [Px, Py]),

    % =:= compares arithmetic VALUES
    ( 2 + 2 =:= 4
      -> writeln('2 + 2 =:= 4  -> true   (values match)') ; true ),

    % = compares STRUCTURE and does not evaluate, so it fails here
    ( 2 + 2 = 4
      -> writeln('2 + 2 = 4    -> true')
      ;  writeln('2 + 2 = 4    -> false  (term 2+2 differs from 4)') ),

    % == checks structural identity without binding anything
    ( 2 + 2 == 2 + 2
      -> writeln('2+2 == 2+2   -> true   (same structure)') ; true ),

    % \= succeeds when two terms cannot be unified
    ( apple \= orange
      -> writeln('apple \\= orange -> true') ; true ),
    halt.

The line 2 + 2 = 4 fails because unification compares the term +(2, 2) against the integer 4 — they are different terms. To compare numbers, you need =:=. This is the most common beginner mistake in Prolog.

Logical Connectives

Prolog’s logical operators are the control flow of the language. Conjunction , (AND), disjunction ; (OR), and negation-as-failure \+ connect goals and steer backtracking.

Create a file named logical.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
:- initialization(main).

likes(mary, wine).
likes(mary, food).
likes(john, wine).

main :-
    % Conjunction (,) succeeds only if BOTH goals succeed
    ( ( likes(mary, wine), likes(mary, food) )
      -> writeln('mary likes wine AND food') ; true ),

    % Disjunction (;) succeeds if EITHER goal succeeds
    ( ( likes(john, food) ; likes(john, wine) )
      -> writeln('john likes food OR wine') ; true ),

    % Negation as failure: \+ succeeds when its goal is NOT provable
    ( \+ likes(john, food)
      -> writeln('john does NOT like food (not provable)') ; true ),
    halt.

Remember that \+ is negation as failure, not logical negation: \+ likes(john, food) succeeds because Prolog cannot prove likes(john, food) from the facts, not because it knows the statement to be false.

Operator Precedence and Terms

Operators carry a priority (1–1200) and a type that determines how a flat sequence of symbols is grouped into a term. Lower priority binds tighter, so * (priority 400) is applied before + (priority 500).

OperatorPriorityNotes
*, /, //, mod400bind tightest
+, -500
is, =:=, =, ==, <, >700comparison / unification
,1000conjunction
;1100disjunction
:-1200rule / directive

Because operators are only sugar, any expression can be taken apart into its underlying term with the =.. (“univ”) operator.

Create a file named precedence.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
:- initialization(main).

main :-
    % * binds tighter than +
    A is 2 + 3 * 4,
    format('2 + 3 * 4   = ~w   (* before +)~n', [A]),

    % parentheses override the default precedence
    B is (2 + 3) * 4,
    format('(2 + 3) * 4 = ~w~n', [B]),

    % operators are just syntax for terms; =.. exposes the structure
    Expr = (1 + 2 * 3),
    Expr =.. Parts,
    format('1 + 2 * 3 as a term: ~w~n', [Expr]),
    format('Functor and args:    ~w~n', [Parts]),
    halt.

The =.. operator reveals that 1 + 2 * 3 is really the term +(1, *(2, 3)), which prints in operator form as 1+2*3. This is why Prolog can let you define custom operators with op/3 — they are nothing more than alternate ways to write ordinary terms.

Running with Docker

1
2
3
4
5
6
7
8
9
# Pull the official SWI-Prolog image
docker pull swipl:stable

# Run each example
docker run --rm -v $(pwd):/app -w /app swipl:stable swipl -q arithmetic.pl
docker run --rm -v $(pwd):/app -w /app swipl:stable swipl -q comparison.pl
docker run --rm -v $(pwd):/app -w /app swipl:stable swipl -q unification.pl
docker run --rm -v $(pwd):/app -w /app swipl:stable swipl -q logical.pl
docker run --rm -v $(pwd):/app -w /app swipl:stable swipl -q precedence.pl

The -q flag suppresses the SWI-Prolog banner, and :- initialization(main). runs the main predicate after the file loads.

Expected Output

Running arithmetic.pl:

8 + 5     = 13
8 - 5     = 3
8 * 5     = 40
17 // 5   = 3
17 mod 5  = 2
2 ^ 8     = 256
10.0 / 4  = 2.5
abs(-9)   = 9
max(3, 7) = 7
sqrt(16)  = 4.0

Running comparison.pl:

3 + 4 =:= 7  -->  true
2 * 5 =\= 11  -->  true
10 > 9  -->  true
5 < 5  -->  false
5 =< 5  -->  true
8 >= 10  -->  false

Running unification.pl:

Unified:  Point = point(3,4)
Px = 3, Py = 4
2 + 2 =:= 4  -> true   (values match)
2 + 2 = 4    -> false  (term 2+2 differs from 4)
2+2 == 2+2   -> true   (same structure)
apple \= orange -> true

Running logical.pl:

mary likes wine AND food
john likes food OR wine
john does NOT like food (not provable)

Running precedence.pl:

2 + 3 * 4   = 14   (* before +)
(2 + 3) * 4 = 20
1 + 2 * 3 as a term: 1+2*3
Functor and args:    [+,1,2*3]

Key Concepts

  • Arithmetic must be evaluated: +, -, *, / build terms; only is/2 (or arithmetic comparison) computes a number.
  • Three kinds of equality: = unifies terms, =:= compares arithmetic values, and == tests structural identity — confusing them is the classic beginner error.
  • Unification is not assignment: = binds variables to make terms match and can run “backwards” to destructure compound terms like point(Px, Py).
  • Comparison operators are goals: each one succeeds or fails rather than returning a boolean; spell less-or-equal as =<, not <=.
  • Logical connectives drive search: , is AND, ; is OR, and \+ is negation as failure (succeeds when a goal is not provable, not when it is false).
  • Precedence groups symbols into terms: lower priority binds tighter (* at 400 before + at 500); parentheses override the default.
  • Operators are sugar for terms: =.. decomposes any expression into [Functor | Args], and op/3 lets you define your own operators.

Running Today

All examples can be run using Docker:

docker pull swipl:stable
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining