Declarative Programming

Logic Programming: Prolog

Logic Programming: Prolog

Table of Contents

Prolog

Key points

Proper List

proper_list([]).
proper_list([Head|Tail]) :- proper_list(Tail).

Append

append([], C, C).
append([A|B], C, [A|BC]) :- append(B, C, BC).

Member

member1(Elt, List) :- append(_, [Elt|_], List).

member2(Elt, [Elt|_]).
member2(Elt, [_|Rest]) :- member2(Elt, Rest).

Logic and Resolution

Interpretations

Views of predicates

Meaning of clauses

grandparent(A, C) :- parent(A,B), parent(B,C).

means “for all terms that A and C may stand for: A is a grandparent of C if there is a term B such that A is a parent of B, and B is a parent of C”

\[\forall A,C (grandparent(A,C) \leftarrow \exists B (parent(A,B) \wedge parent(B,C))).\]

Meaning of predicate definitions

Closed world assumption

Semantics

Finding semantics

e.g.

% P
q(X,Z) :- p(X,Y), p(Y,Z).
% C
p(a,b).
p(b,c).
p(c,d).

Then: \(T_P(C) = \{q(a,c).q(b,d).\}\)

Procedural interpretation

grandparent(A, C) :- parent(A,B), parent(B,C).

Logical interpretation:

\[\forall A,C (grandparent(A,C) \leftarrow \exists B (parent(A,B) \wedge parent(B,C))).\]

Procedural interpretation: to show that A is a grandparent of C, it suffices to show A is a parent of B and B is a parent of C.

Selective Linear Definite Resolution

?- grandparent(queen_elizabeth, prince_harry).

With program:

granpdarent(X,Z) :_ parent(X,Y), parent(Y,Z).
?- parent(queen_elizabeth, Y), parent(Y, prince_harry).
parent(prince_charles, prince_harry).
parent(princess_diana, prince_harry).
?- parent(queen_elizabeth, prince_charles).

SLD Tree

Order of Execution

Backtracking

Indexing

Debugging

Prolog Debugger

Prolog Debugger

Infinite Backtracking Loop

Initial version of reverse

% rev1(X,Y)
% rev1/2 holds when Y is the reverse of the list X
rev1([], []).
rev1([A|BC], CBA) :-
    rev1(BC, CB),
    append(CB, [A], CBA).
% rev3(X,Y)
% rev3/2 holds when Y is the reverse of the list X
rev3(ABC, CBA) :-
    same_length(ABC, CBA),
    rev1(ABC, CBA).

same_length([], []).
same_length([_|Xs], [_|Ys]) :- 
    same_length(Xs, Ys).

Managing nondeterminism

If-then-else ( -> ; ).

Tail Recursion

Tail Recursion Optimisation (TRO)

The Stack

Stack frames

Last call optimisation

Choicepoints

TRO and choicepoints

Accumulator

Accumulating Lists

Tail Recursive rev/2

% rev(BCD, A, DCBA)
% DCBA is BCD reversed, with A appended
rev([], A, A).
rev([B|CD], A, DCBA) :-
  rev(CD, [B|A], DCBA).

Difference Pairs

% inorder tree flatten
flatten(empty, List, List).
flatten(node(L,E,R), List, List0) :-
    flatten(L, List, List1),
    List1 = [E|List2],
    flatten(R, List2, List0).

Homoiconicity

?- clause(append(X,Y,Z), Body).
X = [],
Y = [],
Body = true ;
X = [_1234|_1235],
Z = [_1236|_1237],
Body = append(_1238, Y, _1239).

Prolog interpreter

% A simple Prolog interpreter
interp(Goal) :-
        % if goal is a free variable, throw an exception
        (   var(Goal)
        ->  throw(error(instantiation_error,context(interp/1,interp(Goal))))
        ;   Goal = true
        ->  true
        % conjunction of two goals
        ;   Goal = (G1,G2)
        ->  interp(G1),
            interp(G2)
        % disjunction of two goals
        ;   Goal = (G1 ; G2)
        ->  (   interp(G1)
            ;   interp(G2)
            )
        % negation of a goal
        ;   Goal = \+(G1)
        ->  \+ interp(G1)
        % a known goal
        ;   clause(Goal,Body),
            interp(Body)
        ).

Higher order programming

Currying

% append is called without the 3rd argument
% call has an extra argument: this becomes the 3rd argument of append
?- X = append([1,2],[3]), call(X,L).
X = append([1,2], [3]),
L = [1,2,3]

Writing higher-order code

?- maplist(length, [[a,b],[a],[a,b,c]], Lengths).
Lengths = [2, 1, 3].

All solutions

?- setof(P-C, parent(P,C), List).
List = [prince_charles-prince_william, prince_philip-prince_charles | ...].
?- setof(C, parent(P,C), List).
P = prince_charles,
List = [prince_harry, prince_william] ;
P = prince_philip,
List = [prince_charles] ;
...
>- setof(P, C^parent(P,C), Parents).
Parents = [prince_charles, prince_philip|...].

This is something like saying \(Parents = \{ P | \exists C : parent(P,C) \}\)

Input/Output

?- write('hello '), write('world!').
hello world!
true.
?- write('world!'), write('hello ').
world!hello 
true.

Comparing terms

\[\text{Variables} < \text{Numbers} < \text{Atoms} < \text{Compound Terms}\]

Sorting

Recognising Terms and Variables

Terms

Variables

Multi-mode code

Tail-recursive len that works when length is either known/unknown

len(L, N) :-
    % if N is an integer, use len2
    (   integer(N)
    ->  len2(L, N)
    % N is not an integer, but is not an unbound variable: an error
    ;   nonvar(N)
    ->  throw(error(type_error(integer, N), context(len/2, '')))
    % N is unbound: use len1
    ;   len1(L, 0, N)
    ).

% len1(+L, +N0, -N): length is unknown
len1([], N, N).
len1([_|L, N0, N) :-
    N1 is N0 + 1,
    len1(L, N1, N).

% len2(+L, +N): length is known
len2(L, N) :-
    % this will throw an exception if N is free
    (  N =:= 0
    -> L = []
    ;  N1 is N-1,
       L = [_|L1],
       len2(L1, N1)
    ).

Constraint (Logic) Programming

Specification

Specification comprises:

Purpose:

Common constraint systems

Herbrand Constraints

?- between(1,9,X), 0 =:= X mod 2, X =:= X * X mod 10.

Propagation and Labelling

CLP(FD) Arithmetic Constraints

Arithmetic constraint Description
Expr1 #= Expr2 Expr1 equals Expr2
Expr1 #\= Expr2 Expr1 is not equal to Expr2
Expr1 #> Expr2 Expr1 is greater than Expr2
Expr1 #< Expr2 Expr1 is less than Expr2
Expr1 #>= Expr2 Expr1 is greater than or equal to Expr2
Expr1 #=< Expr2 Expr1 is less than or equal to Expr2
Var in Low..High Low ≤ Var ≤ High
List ins Low..High every Var in List is between Low and High
?- 25 #= X * X.
X in -5\/5.

CLP(FD) Propagation and Labelling

?- 25 #= X * X, label([X]). 
X = -5 ;
X = 5.

Prolog generate and test

?- between(1,9,X), 0 =:= X mod 2, X =:= X * X mod 10.

Constrain and generate

?- X in 1..9, 0 #= X mod 2, X #= X * X mod 10.
X in 2..8,
_12562 mod 10#=X,
X^2#=_12562,
X mod 2#=0,
_12562 in 4..64.

With labelling:

?- X in 1..9, 0 #= X mod 2, X #= X * X mod 10.
X = 6.

Sudoku: Finite Domain Constraints

%% Example Sudoku solver using SWI Prolog's library(clpfd)
:- ensure_loaded(library(clpfd)).

sudoku(Rows) :-
        % each Row is a list in list of lists, there are 9 of them
        length(Rows, 9), 
        % make this a square grid: each row has the same number of 
        % elements as the number of rows
        maplist(same_length(Rows), Rows),
        % append/2 flattens list of lists to a single list
        append(Rows, Vs), 
        % each of those 81 variables is in range 1 to 9
        Vs ins 1..9,
        % apply row constraint: all values are distinct in a row, using clpfd's all_distinct
        maplist(all_distinct, Rows),
        % get Columns from Rows
        transpose(Rows, Columns),
        % apply column constraints
        maplist(all_distinct, Columns),
        Rows = [A,B,C,D,E,F,G,H,I],
        % pass groups of 3 rows in a block to blocks to impose block constraints
        blocks(A, B, C), blocks(D, E, F), blocks(G, H, I).

% blocks takes 3 lists, corresponding to 3 rows: 
% take first 3 values from each list and assert that these 
% 9 values are all distinct.  Then call recusively for the next block
blocks([], [], []).
blocks([A,B,C|Bs1], [D,E,F|Bs2], [G,H,I|Bs3]) :-
        all_distinct([A,B,C,D,E,F,G,H,I]),
        blocks(Bs1, Bs2, Bs3).

puzzle([[5,3,_, _,7,_, _,_,_],
        [6,_,_, 1,9,5, _,_,_],
        [_,9,8, _,_,_, _,6,_],

        [8,_,_, _,6,_, _,_,3],
        [4,_,_, 8,_,3, _,_,1],
        [7,_,_, _,2,_, _,_,6],

        [_,6,_, _,_,_, 2,8,_],
        [_,_,_, 4,1,9, _,_,5],
        [_,_,_, _,8,_, _,7,9]]).

Test with

?- puzzle(Puzzle), time(sudoku(Puzzle)), write(Puzzle).
% 307,876 inferences, 0.039 CPU in 0.039 seconds (100% CPU, 7895949 Lips)
[[5,3,4,6,7,8,9,1,2],
 [6,7,2,1,9,5,3,4,8],
 [1,9,8,3,4,2,5,6,7],
 [8,5,9,7,6,1,4,2,3],
 [4,2,6,8,5,3,7,9,1],
 [7,1,3,9,2,4,8,5,6],
 [9,6,1,5,3,7,2,8,4],
 [2,8,7,4,1,9,6,3,5],
 [3,4,5,2,8,6,1,7,9]]

Linear inequality constraints

% first: ingredient constraints
?- {250*B + 200*C =< 10000},
|   {2*B =< 30},
|   ...
|   {B >= 0}, % number of cakes is non-negative
|   {C >= 0},
|   {Revenue = 4*B + 6.5*C}, % establish cost function
|   maximize(Revenue). % indicate goal
B = 12.0,
C = 2.0,
Revenue = 61.0

Predicates

% the students we know about
student(alice).
student(bob).
student(claire).
student(don).
% who is enrolled in which subjects
enrolled(alice, logic).
enrolled(alice, maths).
enrolled(bob, maths).
enrolled(claire, physics).
enrolled(don, logic).
enrolled(don, art_history).

You can then load this file, and make queries:

?- [classes].
true.
?- student(bob).
true.
?- student(sally).
false.

Variables

?- student(X).
X = alice ;
X = bob ;
X = claire ;
X = don.

The semicolon is input by the user to move to the next possible binding. Enter accepts a binding.

?- enrolled(alice, Subject).
Subject = logic ;
Subject = maths.

Special variable _ matches anything, and each place you write it, it names a different variable.

% is alice enrolled in any subject?
?- enrolled(claire, _).
true.
% is anyone enrolled in any subject?
?- enrolled(_, _).
true ;
true ;
...
true.

Compound queries

% who is taking both maths and logic?
?- enrolled(S, maths), enrolled(S, logic).
S = alice ;
false.
% who is enrolled in either maths or logic?
?- enrolled(S, maths) ; enrolled(S, logic).
S = alice ;
S = don ;
S = alice ;
S = bob.
% who is enrolled in logic but not maths?
?- enrolled(S, logic), \+ enrolled(S, maths).
S = don.

Rules

Facts are clauses specifying that a relationship holds. Rules are clauses that specifies that a relationship holds under certain conditions.

head :- body

The rule specifies that head holds if body holds

% general syntax: the rule specifies that the relationship holds if
head :- body
% two people are classmates if they are enrolled in the same class
classmates(X, Y) :- enrolled(X, Class), enrolled(Y, Class)

Equality

% this shows bob is his own classmate
?- classmates(bob, X).
X = alice ;
X = bob.
% we can use negation and equality to rectify:
?- classmates(X, Y) :- 
    enrolled(X, Class),
    enrolled(Y, Class),
    \+ X = Y.
?- classmates(bob, X).
X = alice ;
false.

Disequality and Negation as Failure

% this doesn't work properly:
?- X \= Y, X = bob, Y = alice.
false.
?- X = bob, Y = alice, X \= Y.
X = bob,
Y = alice.

Terms

Compound Terms

card(clubs, 3)

Lists

Unification

length

Here’s an implementation of Haskell’s take that returns the first N elements of a list

take(N, List, Front) :-
    length(Front, N), 
    append(Front, _, List).

Front is the first N elements of List if the length of Front is N and you can append Front to something to produce List.

member

select

nth0/3

nth0/4

Documenting Modes

% append(+List1, +List2, -List3)
% append(-List1, -List2, +List3)
% List3 is a list of all the elemnts of List1 in order followed
% by the all the elements of List2 in order.

Arithmetic

- the 2nd argument must be a ground term!

```prolog
?- X is 1*A.
ERROR: Arguments are not sufficiently instantiated.
ERROR: In:
...

Arithmetic Predicates

Predicates Description
V is Expr Unify V with the value of expression Expr
Expr1 =:= Expr2 Succeeds if Expr1 and Expr2 are equal
Expr1 =\= Expr2 Succeeds if Expr1 and Expr2 are different
Expr1 < Expr2 Succeeds if Expr1 is strictly less than the value of Expr2
Expr1 =< Expr2 Succeeds if Expr1 is less or equal to the value of Expr2
Expr1 > Expr2 Succeeds if Expr1 is strictly greater than the value of Expr2
Expr1 >= Expr2 Succeeds if Expr1 is greater or equal to the value of Expr2

Arithmetic Expressions

Function Description
-X unary negation (integer or float)
X + Y addition (integer or float)
X - Y subtraction (integer or float)
X * Y multiplication (integer or float)
X / Y division, producing integer or float
X // Y integer division, rounding toward zero
X rem Y integer remainder, same sign as X
X div Y integer division, rounding down
X mod Y integer modulus, same sign as Y
integer(X) round X to the nearest integer
float(X) floating point value of X
ceil(X) smallest integer >= X
floor(X) largest integer =< X
max(X,Y) larger of X and Y (integer or float)
min(X,Y) smaller of X and Y (integer or float)

Semantics

Tail recursion

e.g. we want to make sumList2 tail recursive

sumlist([], 0).
sumlist([E|Es], N) :-
    sumlist(Es, N1),
    N is N1 + E.

As a for loop, this would be:

N = 0
for elem in list:
    N = N + elem

return N

To translate this to Prolog, create a new predicate sumlist/3 with an accumulator

% make sumlist/2 call sumlist/3 with initialised accumulator
sumlist(List, Sum) :-
    sumlist(List, 0, Sum).

sumlist([], Sum, Sum).
sumlist([E|Es], Sum0, Sum) :-
    Sum1 is Sum0 + E,
    sumlist(Es, Sum1, Sum).

Determinism

Indexing

When you call a predicate with some arguments bound, Prolog looks at all the clauses for the predicate to see if some have non-variables in that position of the cluase head. If so, it constructs an index on that argument position. Then Prolog can jump straight to the first clause that matches the query. If it is forced to backtrack, it will jump to the next clause that could match. When it knows there are no more clauses that could match, it removes the choicepoint.

if -> then ; else

ints_between(N0, N, List) :-
    (  N0 < N
    -> List = [N0|List1],
       N1 is N0 + 1,
       ints_between(N1, N, List1)
    ;  N0 = N,
       List = [N]
    ).

Edit this page.