Peano arithmetic
succ, sum, mul, dec and factorial on zero and s/1, plus predicates as boolean functions.
Programming data types
A data type is a construction plus algorithms. The construction is defined by deciding the functors (name and arity) for the terms; the algorithms are predicates, with a mapping from functions to relations.
Recall the features of an algebraic data type: a name for the type; a set of values, expressed by sums of products; and a set of I/O pure functions, often using recursion.
In Prolog we have no types, hence no enforcement:
- the name can only impact the names of values and functions;
- values are expressed by a set of functors, e.g.
cons/2andnil/0; - a function
f : I1, I2, ..., In ↦ Obecomes a predicatep(I1, I2, ..., In, O). Functions are defined (in the body) as a composition of goals:h(X) = f(g(X))becomesh(X, Y) :- g(X, Z), f(Z, Y).ideally using matching on functors. Functions returning booleans might have special treatment.
Example: naturals by Peano numbers
Encoding natural numbers is traditionally the next step after booleans while studying a language's expressiveness. An easy encoding is the unary, Peano one: Z, SZ, SSZ, SSSZ, SSSSZ, ..., so for instance SSZ + SSSZ = SSSSSZ. (Prolog will actually have an ad-hoc management of numbers and booleans.)
The ideas:
- use the functors
s/1andzero/0, e.g.s(s(s(zero))); - the
succfunction is easily obtained bys/1construction; sumis obtained recursively fromsuccors/1:a + 0 = a,a + s(b) = s(a + b);mulis obtained recursively fromsum:a * 0 = 0,a * s(b) = a + (a * b);- we should be able to implement factorial;
- the output is the last argument, as usual.
The solution (peano.pl):
succ(X, s(X)).
sum(X, zero, X).
sum(X, s(Y), s(Z)) :- sum(X, Y, Z).
mul(_, zero, zero).
mul(X, s(Y), Z) :- mul(X, Y, W), sum(W, X, Z).
dec(s(X), X).
factorial(zero, s(zero)).
factorial(s(X), Y) :- factorial(X, Z), mul(s(X), Z, Y).How to read the specification in the relational interpretation:
- the successor of
Xiss(X); - the sum of
XandzeroisX; - the sum of
Xand the successor ofYis the successor ofZ, provided the sum ofXandYisZ; - et cetera.
The goals:
?- succ(s(s(zero)), N)?- sum(s(s(s(zero))), s(s(zero)), N)?- mul(s(s(zero)), s(s(zero)), N)?- dec(s(s(zero)), N)?- dec(zero, N)?- sum(N, M, s(s(s(zero))))The last goal shows the relational nature of the encoding: sum run backwards enumerates every pair of naturals adding up to 3.
The resolution of sum(s(s(s(zero))), s(s(zero)), N) unfolds as:
sum(s(s(s(zero))), s(s(zero)), N)
sum(s(s(s(zero))), s(zero), Z') : {N/s(Z')}
sum(s(s(s(zero))), zero, Z'') : {N/s(Z'), Z'/s(Z'')} ≡ {N/s(s(Z''))}
{N/s(Z'), Z'/s(Z''), Z''/s(s(s(zero)))} ≡ {N/s(s(s(s(s(zero)))))}
The factorial, which the program is built for:
?- factorial(s(s(s(zero))), F)Functions returning a boolean
File greater.pl. The typical Prolog approach is not to add an argument being b_true or b_false, but rather: f : I1, I2, ..., In ↦ bool becomes p(I1, I2, ..., In), and the result is whether the call to the predicate fails or succeeds. In fact, one such function is a predicate. This way, we can only implement what should happen in the positive cases.
greater(s(_), zero).
greater(s(N), s(M)) :- greater(N, M).?- greater(s(zero), s(zero))?- greater(s(s(zero)), s(zero))Multiple output arguments
File nextprev.pl. Again, rather than an output of type pair: f : I1, ..., In ↦ O1 × O2 becomes p(I1, ..., In, O1, O2). In fact, we can conceptually have many inputs and many outputs.
nextprev(s(N), N, s(s(N))).?- nextprev(s(s(zero)), Prev, Next)?- nextprev(zero, Prev, Next)Multiple results
File range.pl. Typically in Prolog, a function returning a lazy list, f : I1, ..., In ↦ LazyList, becomes p(I1, ..., In, O) such that it provides multiple solutions through the inherent resolution mechanism. We should always be prepared for goals to admit many solutions.
% greater/2 is repeated from greater.pl, which range/3 calls.
greater(s(_), zero).
greater(s(N), s(M)) :- greater(N, M).
range(N1, _, N1).
range(N1, N2, N) :- greater(N2, N1), range(s(N1), N2, N).?- range(zero, s(s(s(zero))), N)The resolution tree of range(zero, s(s(s(zero))), N) produces one solution per level, then fails on the last level because greater no longer holds:
range(zero, 3, N)
├── {N/zero}
└── greater(3, zero), range(s(zero), 3, N)
├── {N/s(zero)}
└── greater(3, 1), range(s(s(zero)), 3, N)
├── {N/s(s(zero))}
└── greater(3, 2), range(s(s(s(zero))), 3, N)
├── {N/s(s(s(zero)))}
└── greater(3, 3), ... → NoExercise: Less than or equal
The Peano greater/2 is loaded. Write less_or_equal(A, B): it succeeds when the Peano number A is less than or equal to B (that is, not greater(A, B)), as a plain predicate returning success or failure.