Week 4: Notes

matrices with nested lists

We can represent a vector using a list of numbers, and a matrix using a list of row vectors.

% helper predicates
add(I, J, K) :- K is I + J.
mul(I, J, K) :- K is I * J.

% multiply (scalar S) * (vector V) = (vector V1)
mul_sv(S, V, V1) :- maplist(mul(S), V, V1).

% multiply (scalar S) * (matrix M) = (matrix M1)
mul_sm(S, M, M1) :- maplist(mul_sv(S), M, M1).

% add (vector V1) + (vector V2) = (vector V)
add_vv(V1, V2, V) :- maplist(add, V1, V2, V).

% dot(V1, V2, S): S is the dot product of vectors V1 and V2
dot([], [], 0).
dot([I | L], [J | M], S) :- dot(L, M, S1), S is S1 + I * J.

foldl

The higher-order predicate foldl looks like this:

foldl(+Goal, ?List, ?V0, ?V)

It folds List from the left, applying Goal to each element and an accumulator argument that is initially V0. For example, foldl(G, [a, b, c], V0, V) will apply in turn:

G(a, V0, V1)
G(b, V1, V2)
G(c, V2, V)

foldl is often a convenient alternative to writing a recursive predicate using an accumulator.

% helper predicates
add(I, J, K) :- K is I + J.
max(I, J, K) :- K is max(I, J).

% V is the sum of all numbers in L.
sum(L, V) :- foldl(add, L, 0, V).

% V is the maximum of all numbers in L.
max(L, V) :- foldl(max, L, - inf, V).

% Convert a list of digits (e.g. [4, 5, 6]) to an integer (e.g. 456).
% This is a recursive implementation using an accumulator argument.
to_int1(L, I) :- to_int1(L, 0, I).
to_int1([], V, V).
to_int1([I | L], J, R) :- K is I + 10 * J, to_int1(L, K, R).

% Convert a list of digits to an integer, using foldl.
combine(I, J, K) :- K is I + 10 * J.
to_int(L, I) :- foldl(combine, L, 0, I).

% An efficient implementation of reverse, using foldl.
prepend(X, L, [X | L]).
rev(L, M) :- same_length(L, M), foldl(prepend, L, [], M).

arithmetic expressions

We can easily represent arithmetic expressions using structures in Prolog. For example, we can use 2-argument functors plus and times to represent addition and multiplication. Then we can represent the expression x+ 2x + 7 as

plus(plus(times(x, x), times(2, x)), 7)

Now we can write a predicate to evaluate expressions:

% eval(+E, +X, ?V) evaluates an arithmetic expression E of a single variable x.
% X is the value of x.  The result is V.

eval(x, X, X).

eval(N, _, N) :- integer(N).

eval(plus(A, B), X, V) :- eval(A, X, V1), eval(B, X, V2), V is V1 + V2.

eval(times(A, B), X, V) :- eval(A, X, V1), eval(B, X, V2), V is V1 * V2.

defining lists using structures

Lists are built into Prolog. But if they were not built in, we could easily define them using structures.

To demonstrate this, we'll use a two-element functor cons to build our own lists. (The name cons comes from Lisp and Scheme, which have a similar list representation). cons(H, T) will combine H (the head of the list, i.e. the first element) with T (the rest of the list). In other words, cons(H, T) is like the built-in [H | T]. We will represent the list [2, 3, 4] as

cons(2, cons(3, cons(4, nil)))

Now we can write list predicates just like with built-in lists:

member(X, cons(X, _)).
member(X, cons(_, L)) :- member(X, L).

length(nil, 0).
length(cons(_, L), I) :- length(L, I1), I is I1 + 1.

In fact Prolog's built-in lists are implemented in exactly this way. In other words, lists are just a special case of structures.

defining the integers

Even if integers were not built into Prolog, we could define them using structures. To demonstrate this, we'll use the atom z to represent the number zero, and a single-argument functor s to mean "successor". The number 3 is s(s(s(z))).

Now we can define arithmetic predicates, e.g.

% leq(A, B) is true if A <= B
leq(z, _).
leq(s(I), s(J)) :- leq(I, J).

% add(A, B, C) is true if A + B = C
add(z, I, I).
add(s(I), J, s(K)) :- add(I, J, K).

Unlike Prolog's built-in arithmetic, these predicates will work in all directions. For example:

% Solve the equation 2 + K = 4.
?- add(s(s(z)), K, s(s(s(s(z))))).
K = s(s(z)).

predicates from tutorial

In Thursday's tutorial session we solved most of these exercises. Here are the solutions we saw:

1. cumulative sums

sums(L, M) :- sums(L, 0, M).

sums([], _, []).
sums([X | L], A, [A1 | M]) :- A1 is A + X, sums(L, A1, M).

2. vectors and matrices

% helper predicate
add(I, J, K) :- K is I + J.

% add (vector V1) + (vector V2) = (vector V)
add_vv(V1, V2, V) :- maplist(add, V1, V2, V).

%%% (a) add matrix + matrix = matrix
add_mm(M1, M2, M) :- maplist(add_vv, M1, M2, M).

%%% (b) generate the N x N identity matrix

n_zeroes(L, N) :- length(L, N), maplist(=(0), L).

prep(X, L, [X | L]).

identity(1, [[1]]).

identity(N, [[1 | R] | RR]) :-
    N > 1, N1 is N - 1, identity(N1, M),
    n_zeroes(R, N1), maplist(prep(0), M, RR).

3. defining multiplication

% leq(I, J) is true if I <= J
leq(z, _).
leq(s(I), s(J)) :- leq(I, J).

% add(I, J, K) is true if I + J = K
add(z, I, I).
add(s(I), J, s(K)) :- add(I, J, K).

% mul(I, J, K) is true if I * J = K
mul(z, _, z).
mul(s(I), J, K) :-
  leq(s(I), K), leq(J, K),   % guarantee termination
  mul(I, J, K1), add(J, K1, K).