These are the notes accompanying the slides Additional Features of Prolog and the ISO Standard (3_prolog_language.pdf) of the Computational Logic course.
This is the lecture version: it follows the slides in order, but keeps much more of what was actually said in class — the asides, the reasons, the second explanations, and the things that were only ever said out loud. Where the slide gives a bullet, the lecture usually gave a paragraph and an example.
How to use this interactive document:
The code boxes, marked by a question mark (?) in the right top corner, allow interaction. Feel free to edit any program and make all the queries you want!
Prolog is a real, practical programming language based on that paradigm — the most popular one, and standardized by ISO. What does it add?
A fixed control. By default the search rule is depth-first and the computation rule is left-to-right. You no longer choose; the language has chosen for you. That is a loss of generality, and it buys a great deal: a program with a fixed, known execution order can be compiled into fast code — including native code — and then it runs at roughly the speed of any other language.
A large library of built-in predicates. Some declarative, some not — but all of them efficient. Arithmetic is the obvious case: built-in integer arithmetic is enormously faster than the Peano arithmetic we wrote in the previous part.
Things that go beyond first-order logic. Higher-order predicates; meta-logical predicates, which can ask whether something is still a variable — a question you cannot even state in pure logic; and modification of the program itself while it runs, programs that rewrite themselves as they go.
A program without search — one clause per procedure, or several clauses of which only one ever matches a given call, so the clauses work like a case statement rather than as alternatives — runs, under the left-to-right rule, exactly like its imperative or strict functional counterpart. Which means any imperative or functional program can be transliterated into Prolog and will behave the same way. The one adjustment is that you cannot overwrite a variable; but you just use a fresh one, and real systems offer imperative assignment anyway if you insist.
A program with search — at least one predicate where more than one clause matches — has no direct counterpart in imperative or functional programming. (List comprehensions are the nearest thing.)
And which of the two you have depends on the call mode. append/3 given two lists behaves exactly like an imperative concat; given the third list it becomes a search. Same program.
That list is also the plan of this part of the course, and of these notes.
If we stopped at the previous slide we would already have most of the difference between Prolog and pure logic programming — at least for pure Prolog programs, where the differences are that it runs depth-first and that this lets you control the search. Everything below is what the ISO standard adds on top. Other systems have plenty more, but those extensions are system-specific; the standard is what everyone agrees on.
For the system used in this course, see the separate part on Developing Programs with a Logic Programming System.
There are some new data types.
Numbers — real numbers now, not Peano numbers: 0, 999, -77, 5.23, 0.23e-5. And a very nice property of many current systems, Ciao among them: integers have infinite precision. There is no word size, and no overflow.
?- X is 1234567890123456789012345678901234567890 * 987654321.Expected answer:
X = 1219326311248285321124828532112482853211126352690 ?Strings are just lists of character codes. "foo" is the list of the ASCII codes of f, o and o:
?- X = "foo".Expected answer:
X = [102,111,111] ?Which is honest, but not very readable. There is a Prolog flag for it — flags are parameters of the system you can change at will — and this one is worth remembering:
?- set_prolog_flag(write_strings, on), X = "foo".Expected answer:
X = "foo" ?Now, when the system prints a list of numbers that all happen to be printable character codes, it prints it as a string. If the list contains something that is not, it gives up and prints the list.
Comments are % to the end of the line, or /* ... */ as in C.
The declaration is
:- op(<precedence>, <type>, <operator(s)>).Precedence is an integer from 1 to 1200. The general rule: the operator with the highest precedence number is the principal functor. So if + has a higher precedence number than /, then a+b/c is a+(b/c), that is, +(a,/(b,c)). Note that some other languages number precedence the other way round. And parentheses always work: /(+(a,b),c) is (a+b)/c.
Type is one of nine mnemonics, and they read as pictures — f is the operator, x and y are the arguments:
Operators is a single atom or a list of atoms.
?- write_canonical(1+2/4^2), nl.Expected answer:
+(1,/(2,^(4,2)))So 4^2 first, then 2 divided by that, then 1 plus all of it — which is what you wanted, but now you know.
:- module(_,_,[]). :- op(500, xfx, is_father_of). john is_father_of peter. peter is_father_of jim.
?- is_father_of(X, Y).Expected answer:
X = john, Y = peter ?The operator changes only how the term is written and read — internally it is still is_father_of(john,peter), as the query above shows by using the ordinary notation on a fact written in operator notation.
?- op(500, xfx, is_father_of). yes ?- X is_father_of Y. X = john, Y = peter ?Why the locality? Because operators are how you define language extensions. One module can use functional syntax, another can use an imperative syntax, and neither disturbs the other. Without locality you could not compose them.
Which brings us to the point of the slide:
| standard notation | operator notation |
|---|---|
| +(a,/(b,c)) | a+b/c |
| is(X, mod(34, 7)) | X is 34 mod 7 |
| <(+(3,4),8) | 3+4 < 8 |
| =(X,f(Y)) | X = f(Y) |
| -(3) | -3 |
| spy(/(foo,3)) | spy foo/3 |
| :-(p(X),q(Y)) | p(X) :- q(Y) |
| :-(p(X),','(q(Y),r(Z))) | p(X) :- q(Y),r(Z) |
Look at the last two lines. A clause is a term: the principal functor is :-, the neck; the first argument is the head; the second is the body, which for a conjunction is the term ','(q(Y),r(Z)) — nested to the right, exactly like the dots of a list.
That is why assert/1 — add a clause to the running program — will be a one-liner later on: you build a term and hand it over. Nothing has to be converted.
One syntactic trap. Parentheses must be used around operators whose priority is higher than 1000, the priority of ,. So you write ..., assert( (p :- q) ), ... — without the inner parentheses the comma-separated argument list wins and you get a syntax error.
:- op( 1200, xfx, [ :-, --> ]).
:- op( 1200, fx, [ :-, ?- ]).
:- op( 1150, fx, [ mode, public, dynamic,
multifile, block, meta_predicate,
parallel, sequential ]).
:- op( 1100, xfy, [ ; ]).
:- op( 1050, xfy, [ -> ]).
:- op( 1000, xfy, [ ',' ]).
:- op( 900, fy, [ \+, spy, nospy ]).
:- op( 700, xfx, [ =, is, =.., ==, \==, @<, @>, @=<, @>=,
=:=, =\=, <, >, =<, >= ]).
:- op( 550, xfy, [ : ]).
:- op( 500, yfx, [ +, -, #, /\, \/ ]).
:- op( 500, fx, [ +, - ]).
:- op( 400, yfx, [ *, /, //, <<, >> ]).
:- op( 300, xfx, [ mod ]).
:- op( 200, xfy, [ ^ ]).Read it as a map. It tells you where you can slot a new operator in: above or below the clause neck, the comma, the disjunction, the implication, unification, the arithmetic comparisons, the arithmetic operators. In Ciao you can even unload them if you want a completely different language; they are loaded by default for convenience.
:- module(_, _).
grandparent(C,G) :-
parent(C,P),
parent(P,G).
parent(C,P) :- father(C,P).
parent(C,P) :- mother(C,P).
father(charles, philip).
father(ana, george).
mother(charles, ana).?- grandparent(charles, X).Expected answer:
X = george ?Follow it on the tree: parent(charles,P) first tries father, giving P = philip; then parent(philip,G) has no solution at all, and the whole branch fails. Backtracking returns to the last choice point, takes mother(charles,ana), and parent(ana,G) then succeeds with george.
The type of arithmetic terms. It is defined exactly the way we defined types in the pure part:
Slide 11 gives the type itself, and it is worth looking at as a type: a long but perfectly ordinary recursive definition, in the same shape as nat/1 or list/1, where every alternative but the first is built from smaller arithmetic expressions. Its real name in Ciao is arithexpression/1 (core/engine/arithmetic.pl defines it); the slide — and the definition below — write arithexpr for short, to fit.
arithexpr := | num | + arithexpr | - arithexpr | ++ arithexpr | -- arithexpr | arithexpr + arithexpr | arithexpr - arithexpr | arithexpr * arithexpr | arithexpr // arithexpr | arithexpr / arithexpr | abs(arithexpr) | sign(arithexpr) | float_integer_part(arithexpr) | float(arithexpr) | float_fractional_part(arithexpr) | arithexpr ** arithexpr| exp(arithexpr) | log(arithexpr) | sqrt(arithexpr) | sin(arithexpr) | cos(arithexpr) | atan(arithexpr) | [arithexpr] | ...num is the base case — a number is an arithmetic term — and [arithexpr] is the one that looks odd until you remember that "a" is a list of character codes: a one-element list evaluates to its element, which is how X is "a" gives 97.
Beyond those there are rem, mod, gcd, the shifts and the bitwise operators, and integer, truncate, floor, round, ceiling. The manuals have the complete list.
?- X = 3+4/2, write_canonical(X), nl.Expected answer:
+(3,/(4,2)) X = 3+4/2 ?X is bound to a data structure. Nothing was computed.
The two things you can do with an arithmetic term are evaluate it and compare it.
Z is X evaluates X — which must be an arithmetic term — and unifies the result with Z. The comparison operators <, >, =<, >=, =:= (arithmetic equality) and =\= (arithmetic inequality) evaluate both arguments and compare the results.
?- X is 3*3//2.Expected answer:
X = 4 ?
?- Z = 3//2, X is 3+Z.Expected answer:
X = 4, Z = 3//2 ?Look carefully at that second one. Z is bound to the term 3//2 — not to 1. The evaluation happens only at the is, and at that moment the whole structure 3+Z is evaluated, Z and all:
?- X = 1 + Y, Y = 3/2, Z is X.Expected answer:
X = 1+3/2, Y = 3/2, Z = 2.5 ?Y is not 1.5 and is not a rational number; it is the term /(3,2). is/2 interprets the structure handed to it, and only then does arithmetic happen.
Failure — the goal is well formed, evaluates fine, and simply is not true. The system backtracks as usual:
?- X=3, Y=4, Y < X+1.Expected answer:
no
?- X=3, Y=4, X is Y+1.Expected answer:
noError — the goal cannot be evaluated at all, and execution is aborted:
?- Y=4, Y < a+1.Expected answer:
{ERROR: arithmetic:is/2, arg 2 - expected an arithmetically evaluable
expression, found /(a,0)}Read that message; it is a good one. The predicate is is/2 (which stands in for all the arithmetic operators) in module arithmetic; the problem is in argument 2; it expected something evaluable and found a — printed in name/arity form as /(a,0), i.e. the atom a.?- X is Z+1.Expected answer:
{ERROR: arithmetic:is/2, arg 2 - argument is not sufficiently instantiated}An unbound variable has no value, so there is nothing to evaluate. Note that the pure version would have worked perfectly: plus(Z,s(0),Y) is reversible and would have generated candidates. is/2 cannot, because it is a function.?- X=3, X =:= f(a).Expected answer:
{ERROR: arithmetic:=:=/2, arg 2 - expected an arithmetically evaluable
expression, found /(f,1)}So Peano arithmetic was declarative but slow; is/2 is fast but not declarative; constraints are both. That is the subject of the last part of the course, and one of the things constraint logic programming does is give arithmetic its declarativeness back.
plus(X,Y,Z) :- Z is X + Y.It works in one direction only — X and Y must be bound to arithmetic terms. We have lost the recursive structure of the numbers, and with it reversibility. (The meta-logical predicates of a later slide let you recover some of it, by testing which arguments are bound and choosing what to compute.) What we have won is a great deal of performance.
Factorial makes the trade visible. Here are both versions side by side:
:- module(_,_).
% a) Using Peano arithmetic:
peano_factorial(0,s(0)).
peano_factorial(s(N),F) :-
peano_factorial(N,F1),
peano_times(s(N),F1,F).
peano_times(0,_,0).
peano_times(s(X),Y,Z) :-
peano_times(X,Y,W),
peano_plus(W,Y,Z).
peano_plus(0,Y,Y) :- nat(Y).
peano_plus(s(X),Y,s(Z)) :- peano_plus(X,Y,Z).
nat(0).
nat(s(X)) :- nat(X).
% b) Using ISO-Prolog is/2 and >/2:
factorial(0,1).
factorial(N,F) :-
N > 0,
N1 is N-1,
factorial(N1,F1),
F is F1*N.The two are the same algorithm. The Peano one is pure and reversible; try it on 5 and it is fine, on 6 it takes a noticeable moment, on 7 you stop waiting.?- factorial(7,F).Expected answer:
F = 5040 ?Instantaneous — and so is factorial(1000,F), whose answer has 2568 digits. Imagine that number built out of nested s/1 terms; that is why the Peano version cannot be printed, never mind computed.
wrong_factorial(0,1).
wrong_factorial(N,F) :-
N > 0,
N1 is N-1,
F is F1*N, % F1 has no value yet!
wrong_factorial(N1,F1).?- wrong_factorial(3,F).Expected answer:
{ERROR: arithmetic:is/2, arg 2 - argument is not sufficiently instantiated}At the moment F is F1*N is reached, F1 is a fresh variable — the recursive call that will bind it has not run yet. In pure logic programming this would have been survivable: the pure times/3 would have generated candidates and the recursive call would have checked them, inefficiently but correctly. is/2 cannot do that.The same reason kills the reverse direction:
?- factorial(N,6).Expected answer:
{ERROR: arithmetic:>/2, arg 1 - argument is not sufficiently instantiated}factorial/2 with the built-in arithmetic is a function, not a relation.They are unary relations that check the type of a term:
They behave as if defined by a (possibly infinite) table of facts — integer(1). integer(2). … — and they either succeed or fail; they never raise an error.
That behaviour is outside first-order logic, because what it really tests is the instantiation state of a variable — a question that cannot even be phrased in logic.
?- integer(1).Expected answer:
yes
?- integer(1.0).Expected answer:
no
?- integer(X).Expected answer:
no
?- X = 1, integer(X).Expected answer:
X = 1 ?
?- integer(X), X = 1.Expected answer:
noTwo orders, two different answers — so integer/1 is not a pure predicate. Contrast nat/1: X = s(s(s(0))), nat(X) and nat(X), X = s(s(s(0))) both succeed. The second one has to backtrack through 0, s(0), s(s(0)) before it gets there — less efficient, but logically consistent, which is the whole point.
Is integer/1 therefore wrong? No — it is impure, and you have to know it. If you want a pure integer test, use Peano numbers or constraints. If you use these, then that part of your program can no longer be read as logic; you are reading an imperative program.
plus(X,Y,Z) :- Z is X + Y.only runs in one direction. Guard the clauses with number/1 and you can cover three:
:- module(_,_). plus(X,Y,Z) :- Z is X + Y. plus2(X,Y,Z) :- number(X), number(Y), Z is X + Y. plus2(X,Y,Z) :- number(X), number(Z), Y is Z - X. plus2(X,Y,Z) :- number(Y), number(Z), X is Z - Y.
?- plus2(4,5,Z).Expected answer:
Z = 9 ?
?- plus2(X,5,9).Expected answer:
X = 4 ?Give it any two of the three and it computes the third. But partitioning a number into two others is beyond it:
?- plus2(X,Y,9).Expected answer:
no
The honest answer, of course, is neither no nor an error: it is the equation X = 9-Y. That is exactly what a constraint system answers, and if you later add Y = 2 the equation resolves itself. By the time you have handled enough cases here by hand, you will have written a small part of constraint logic programming yourself. Which is the real solution, and the last part of the course.
?- functor(t(b,a), F, A).Expected answer:
A = 2, F = t ?
?- functor(Term, f, 3).Expected answer:
Term = f(_,_,_) ?functor(Vector, v, 100) builds a hundred-argument term in one step — which is the beginning of an array. (In some systems the arity is limited to 256 by default; there are libraries for unbounded arrays.)
?- T = date(9,'February',1947), arg(3,T,X).Expected answer:
T = date(9,'February',1947), X = 1947 ?The same result comes from plain unification — T = date(_,_,X) — and where you have a fixed, known shape that is the better style. arg/3 earns its keep when the position is computed, as in the array code below.
Building and filling in one go:
?- functor(Array,array,5), arg(1,Array,black), arg(5,Array,white). Array = array(black,_,_,_,white) ?
?- arg(2, [a,b,c,d], X).Expected answer:
X = [b,c,d] ?Not b. Because a list is not a special data type: [a,b,c,d] is the term '.'(a,[b,c,d]), a binary structure whose second argument is the tail. Confirm it directly with functor([a,b,c,d],F,A), which answers F = '.', A = 2.
:- module(_,_).
add_arrays(A,B,C):-
functor(A,array,N), % gets length N from array A
functor(B,array,N), % checks that B has the same length
functor(C,array,N), % creates C to hold the result
add_elements(N,A,B,C).
add_elements(0,_A,_B,_C).
add_elements(I,A,B,C):-
I>0,
arg(I,A,AI),
arg(I,B,BI),
arg(I,C,CI),
CI is AI + BI,
I1 is I - 1,
add_elements(I1,A,B,C).
% Alternative, using lists instead of structures:
add_lists([],[],[]).
add_lists([X|Xs],[Y|Ys],[Z|Zs]):-
Z is X + Y,
add_lists(Xs,Ys,Zs).?- add_arrays(array(1,2,3), array(4,5,6), R).Expected answer:
R = array(5,7,9) ?Note the elegance of the length check: writing the same N in all three functor/3 calls both reads the length off A, checks that B has it, and creates C with it. Walk through it once, running forwards with the first two arrays given:
This is what unification buys: a formal theory that gives you these memory operations directly, with no need to model the memory yourself.
Note also that in the recursive call the three arrays are passed unchanged — the same pointers go round every time. It is the index that moves.
Mismatched lengths simply fail:
?- add_arrays(array(1,2,3), array(4,5), R).Expected answer:
no
Which version is better? The list one is shorter and easier to write — a recursion is a more natural way to express this than a loop, and a loop always needs an extra variable that has nothing to do with the problem. The array version wins when you want random access: jump to the first element, then the last, then back — arg/3 does that in constant time and a list does not.
There is one thing to be said for the verbose loop, though: you can see exactly what it does. In an imperative language it is not always obvious whether the test happens before or after the body, or where the decrement lands. Here all of it is written out in front of you.
?- add_arrays(array(1,2,3),A2,array(5,7,9)).
{ERROR: arithmetic:is/2, arg 2 - argument is not sufficiently instantiated}By the time the loop reaches CI is AI + BI we are giving CI and AI and asking for BI, and is/2 cannot run backwards. The fix is the one from slide 15: replace is/2 with the guarded plus2/3, and array subtraction starts working too.What it still will not do is take one array and enumerate all the pairs of arrays that add to give it. For that you would need Peano arithmetic — which you could write — or a constraint, which is reversible arithmetic and runs in every direction. That is the last part of the course.
:- module(_,_).
:- use_package(fsyntax). % use functional notation
:- op(250,xfx,@). % define @ as an infix operator
T@N := A :- arg(N,T,A). % define @ to call arg/3
:- fun_eval @ / 2. % evaluate all occurrences of @/2
array(A,N) :- functor(A,array,N).
:- fun_eval arith(true). % evaluate arithmetic expressions (I-1)
add_arr(A,B,C):-
array(A,N),
array(B,N),
array(C,N),
add_els(N,A,B,C).
add_els(0,_,_,_).
add_els(I,A,B,C) :-
I>0,
C@I is A@I + B@I,
add_els(I-1,A,B,C).?- add_arr(array(1,2,3), array(4,5,6), R).Expected answer:
R = array(5,7,9) ?C@I is A@I + B@I — which is what we wanted to write in the first place. The point is not the convenience; it is that the notation was added by the user, with an operator declaration and one clause, and the rest of the language did not have to know about it.
And nothing forces @. Declare sub as an xfx operator and write a sub 3; or use : and write a:3. With functional notation you can go further and chain them — a sub 3 sub 2 reads element (3,2) of a matrix — because the syntax is extensible and you decide how convenient you want it to be.
:- module(_,_).
subterm(Term,Term). % a) a term is always a subterm of itself
subterm(Sub,Term):- % b) the arguments are also subterms:
nonvar(Term), % check Term is not a free variable
functor(Term,_F,N),% N is the number of arguments of Term
n_to_one(N, J), % J is a natural between N and 1
arg(J,Term,Arg), % Arg is the J-th argument of Term
subterm(Sub,Arg). % Sub is a subterm of Arg
n_to_one(N, N) :- N > 0.
n_to_one(N, X) :- N > 1, N1 is N-1, n_to_one(N1, X).The base case is by definition: anything is a subterm of itself, and "is" here means unifies with. The recursive case walks into the structure: take the arity with functor/3, use n_to_one/2 to generate an argument position J — from N down to 1, on backtracking — pick that argument out with arg/3, and recurse. nonvar/1 stops it from trying to take a free variable apart.?- subterm(f(a), g(b,f(a))).Expected answer:
yes
?- subterm(f(b), g(b,f(a))).Expected answer:
noAnd now the part worth pausing on. Because functor/3 and arg/3 both work backwards well enough, the same program enumerates the subterms:
?- subterm(X, g(b,f(a))).Expected answer:
X = g(b,f(a)) ? ; X = f(a) ? ; X = a ? ; X = b ? ; noFour subterms, in that order — the whole term, then down into the second argument (n_to_one/2 counts down, so the last argument comes first), then the first. Ask yourself how many lines this would take in another language, and whether that version would also generate.
?- date(9,february,1947) =.. L.Expected answer:
L = [date,9,february,1947] ?
The interesting direction is backwards — from a list of pieces, build a term:
?- F = '+', X =.. [F,a,b].Expected answer:
F = +, X = a+b ?Look at what just happened: F is a variable, and it ended up as the functor of the term. Push it one step further and the term gets evaluated:
?- F = '+', X =.. [F,3,5], Z is X.Expected answer:
F = +, X = 3+5, Z = 8 ?
Use it only when strictly necessary. It is expensive in both time and memory.
:- module(_,_).
deriv(sin(X), X, cos(X)).
deriv(cos(X), X, -sin(X)).
% Chain rule:
deriv(FG_X, X, DF_G * DG_X):-
FG_X =.. [_, G_X],
deriv(FG_X, G_X, DF_G),
deriv(G_X, X, DG_X).The chain rule says the derivative of f(g(x)) with respect to x is the derivative of f(g) with respect to g, times the derivative of g with respect to x. The univ call is what extracts the inner function G_X out of the composite term — the functor is discarded with _, because the rule does not care which outer function it is.?- deriv(sin(cos(x)), x, D).Expected answer:
D = cos(cos(x))* -sin(x) ?The outer derivative cos(cos(x)), times the inner one -sin(x).
?- name(hello, S).Expected answer:
S = [104,101,108,108,111] ?
?- name(A, "hello").Expected answer:
A = hello ?(With set_prolog_flag(write_strings,on) the first answer prints as S = "hello".)
Backwards, name/2 creates atoms out of nothing — and combined with univ that means creating new functors, and therefore new structures, at run time:
?- name(N, "foo"), T =.. [N,b,c].Expected answer:
N = foo, T = foo(b,c) ?That is meta-programming: building new symbols, identifiers and functors while the program runs.
?- X='1', atom(X), name(X,S), name(Y,S), number(Y).Expected answer:
S = [49], X = '1', Y = 1 ?We started with an atom and ended with a number, and the query succeeded — which is exactly the problem. The ISO standard fixes it by splitting name/2 into two unambiguous predicates:
?- atom_codes(X, "1").Expected answer:
X = '1' ?
?- number_codes(X, "1").Expected answer:
X = 1 ?You choose which one you want; there is nothing left to guess.
?- var(X), X = f(a).Expected answer:
X = f(a) ?
?- X = f(a), var(X).Expected answer:
noSame two goals, opposite order, different answers — the impurity test from the previous section, applied again. nonvar/1 behaves as the exact mirror image.
?- X = f(Y), compound(X).Expected answer:
X = f(Y) ?
?- X = f(Y), ground(X).Expected answer:
nof(Y) is compound but not ground, because Y is still free. A ground (or closed) term is one with no variables inside it at all.
What are they for? Two things: to control goal order for efficiency, and to restore some flexibility to programs that use non-reversible built-ins. The plus2/3 of slide 15 could have been written with nonvar/1 instead of number/1 — though number/1 is better there, because it also checks that what arrived is actually a number.
:- module(_,_). len([],0). len([_|T],N) :- len(T,TN), N is TN+1.
?- len([a,b,c], N).Expected answer:
N = 3 ?
?- len(L, 3).Expected answer:
L = [_,_,_] ?A list of three holes — just as functor/3 gave us an array of holes. So do not assume that a program stops being reversible the moment it contains an is/2.
What we can do, though, is pick a different implementation depending on the direction, for efficiency. That is what the meta-logical predicates make possible:
:- module(_,_).
mylength(Xs,N):-
var(Xs),
integer(N),
create_list(N,Xs).
mylength(Xs,N):-
nonvar(Xs),
compute_length(Xs,N).
create_list(0,[]).
create_list(N,[_|Xs]):-
N > 0,
N1 is N - 1,
create_list(N1,Xs).
compute_length(L,N) :-
compute_length_(L,0,N).
compute_length_([],N,N).
compute_length_([_|T],A,N) :-
NA is A+1,
compute_length_(T,NA,N).?- mylength([a,b,c], N).Expected answer:
N = 3 ?
?- mylength(L, 3).Expected answer:
L = [_,_,_] ?The first clause fires when Xs is unbound and N is an integer: there is no list to walk, so it loops on the number instead, laying down one cons cell per iteration until it hits zero.
Why bother? Because create_list/2 is tail recursive — the recursive call is last, so the local variables are dead by the time it is made, the stack frame can be discarded, and the whole thing runs as a loop in constant memory. len/2 in that direction is not: its is/2 sits after the recursive call, so computing a length of a million pushes a million frames before any of them can return.
The second direction gets the same treatment, with an accumulating parameter. compute_length_/3 carries a counter that starts at 0 and grows by one per cons cell; the third argument is passed untouched all the way down and receives the answer at the base case. The addition now happens before the recursive call, so this version is tail recursive too.
Specification, executable specification, and efficient program — all in one notation. That is something Prolog is genuinely good at.
Identity tests just look:
?- f(X) == f(X).Expected answer:
yes
?- f(X) == f(Y).Expected answer:
no
Are X and X the same variable? Yes. Are X and Y? No. Now unify them and ask again:
?- X = Y, X == Y.Expected answer:
Y = X ?Yes — so ==/2 knows whether two variables have been aliased. Read in terms of the pointer picture from the previous deck: ==/2 tells you whether two pointers point at the same place. Which means you can inspect aliasing at run time, and write something very close to imperative data structure code.
Term ordering gives a total order over all terms — which is algorithmically very convenient, because unless two terms are identical, one of them is definitely the larger:
?- f(a) @> f(b).Expected answer:
no
?- f(b) @> f(a).Expected answer:
yesThe comparison is recursive and works to any depth, on a structure of any size, without your having to navigate it.
?- f(a,b) @> g(a).Expected answer:
yesyes, even though g comes after f alphabetically — because f(a,b) has arity 2 and g(a) has arity 1, and arity is compared first. With equal arities the name decides, and f(a,b) @> g(a,b) is no as expected.
The reason is the implementation. Variables live in the heap, and comparing them compares their heap addresses — which is precisely the information a system wants, since a younger variable will be deallocated sooner on backtracking.
The standard calls this implementation dependent, and it is; but most systems, Ciao among them, behave this way.
:- module(_,_).
subterm(Term,Term).
subterm(Sub,Term):-
nonvar(Term),
functor(Term,_F,N),
n_to_one(N, J),
arg(J,Term,Arg),
subterm(Sub,Arg).
subterm_ng(Sub,Term) :- % a) identical, rather than unifiable
Sub == Term.
subterm_ng(Sub,Term) :-
nonvar(Term),
functor(Term,_F,N),
n_to_one(N, J),
arg(J,Term,Arg),
subterm_ng(Sub,Arg).
n_to_one(N, N) :- N > 0.
n_to_one(N, X) :- N > 1, N1 is N-1, n_to_one(N1, X).?- subterm(f(a), g(X,k)).Expected answer:
X = f(a) ?It says yes, and instantiates X to make it so. Which answer is right depends entirely on what you are doing. Logically it is impeccable: f(a) can be a subterm of g(X,k), and this is what you want if the program is to run backwards. But for meta-programming, where you care about the variables themselves, you want a no: f(a) does not literally occur there.
Change the base case from unification to identity and you get the other reading:
?- subterm_ng(f(a), g(X,k)).Expected answer:
no
?- subterm_ng(f(a), g(h(f(a)),k)).Expected answer:
yesOn ground terms the two behave identically. They part company exactly where a variable would have been instantiated.
:- module(_,_). insert_ee([], Item, [Item]). insert_ee([H|T], Item, [H|T]) :- H == Item. insert_ee([H|T], Item, [Item, H|T]) :- H @> Item. insert_ee([H|T], Item, [H|NewT]) :- H @< Item, insert_ee(T, Item, NewT). % The same program written with unification and arithmetic comparison: insert_unif([], Item, [Item]). insert_unif([H|T], Item, [H|T]) :- H = Item. insert_unif([H|T], Item, [Item, H|T]) :- H > Item. insert_unif([H|T], Item, [H|NewT]) :- H < Item, insert_unif(T, Item, NewT).
?- insert_ee([a,b,e], c, L).Expected answer:
L = [a,b,c,e] ?Because @> and @< order any terms, this works on symbols and not just numbers. Now compare with the version that uses =/2 and the arithmetic comparisons:
?- insert_unif([a,b,e], c, L).Expected answer:
{ERROR: arithmetic:>/2, arg 1 - expected an arithmetically evaluable
expression, found /(a,0)}>/2 tried to evaluate the atom a. And the difference shows up again with an unbound item:?- insert_ee([a,b,e], X, L).Expected answer:
L = [X,a,b,e] ?
?- insert_unif([a,b,e], X, L).Expected answer:
L = [a,b,e], X = a ?insert_ee/3 places the unbound X at the front — correctly, since variables precede atoms in the standard order. insert_unif/3 instead unifies X with a and declares the item already present. It is the == versus = distinction, doing visible damage.
Stream control — where are we reading from and writing to:
| predicate | what it does |
|---|---|
| see(File) | File becomes the current input stream |
| seeing(File) | the current input stream is File |
| seen | close the current input stream |
| tell(File) | File becomes the current output stream |
| telling(File) | the current output stream is File |
| told | close the current output stream |
Term I/O — and this is the powerful group:
| predicate | what it does |
|---|---|
| write(X) | write the term X on the current output stream |
| nl | start a new line on the current output stream |
| read(X) | read a term (finished by a full stop) from the current input stream and unify it with X |
Character I/O, for binary work:
| predicate | what it does |
|---|---|
| put_code(N) | write the character with code N |
| get_code(N) | read the next character code and unify it with N |
?- read(X), length(X,N), write(N). typed at the prompt, answered with [1,2,3,4,5]., writes 5. Five, not the number of characters — because what came back was the list.
And write/1 is the exact converse. Hand it a pointer into the middle of an arbitrarily complicated structure and it prints whatever is there, to any depth, with no navigation code on your part.
Stream-based versions of all of these also exist, and are closer to what other languages do: open(File,Mode,S) with Mode one of read, write or append, and close(S); then write(S,X), nl(S), read(S,X), put_code(S,N), get_code(S,N). Use these when you need several files open at once — each gets its own stream identifier.
Operators. write/1 uses whatever operator declarations are in force, so a+b is written a+b — which only reads back if the same declarations are in force at reading time. write_canonical/1 writes without operators:
?- tell(bar), write(a+b), write('.'), told. % bar contains: a+b.
?- tell(baz), write_canonical(a+b), write('.'), told. % baz contains: +(a,b).The canonical form is the safe one: it can always be read back, whatever operators happen to be declared.:- module(_,_).
:- use_module(library(stream_utils)).
write_list_to_file(L,F) :-
telling(OldOutput), % grab the current output stream
tell(F), write_list(L), told, % write into F, close
tell(OldOutput). % restore the previous output stream
write_list([]).
write_list([X|Xs]):-
write(X),
write('.'),
nl,
write_list(Xs).
show_file(F) :-
file_to_string(F,L),
format("~s",[L]).?- write_list_to_file([f(a), m, g(k)], foo), show_file(foo).Expected answer:
f(a). m. g(k). yesThe telling/1 … tell(OldOutput) sandwich is the point of the example: the predicate may be called from a program that was already writing somewhere, and it puts the output stream back where it found it.
Reading the file back one term at a time:
?- see(foo), read(X), read(Y), read(Z), read(W), seen. W = end_of_file, X = f(a), Y = m, Z = g(k) ?The fourth read returns the atom end_of_file. Reading past that raises a past-end-of-stream error, as in any other language.
All of these input/output predicates are side effects. They are not logical, their order matters absolutely, and backtracking does not undo them. Everything the pure part of the course said about reading a program as a set of logical statements stops applying the moment one of these appears.
When executed, a cut prunes exactly two things:
:- module(_,_).
s(1) :- write('\n Clause 1 of s/1 succeeded.').
s(2) :- write('\n Clause 2 of s/1 succeeded.').
l(a) :- write('\n Clause 1 of l/1 succeeded.').
l(b) :- write('\n Clause 2 of l/1 succeeded.').
r(8) :- write('\n Clause 1 of r/1 succeeded.').
r(9) :- write('\n Clause 2 of r/1 succeeded.').
m(e) :- write('\n m/1 succeeded.').
p(X,_Y) :-
l(X),
write('\n Reached end of clause 1 of p.').
p(X,_Y) :-
r(X),
!, % Cut
write('\n Executed cut (no more alternatives of r/1 or p/1)'),
write('\n At end of clause 2 of p.').
p(X,_Y) :-
write('\n Reached clause 3 of p.'),
m(X).Run ?- s(A), p(X,Y). and ask for more solutions. The write/1 calls make the path visible.Trace it by hand first. s(A) succeeds with A = 1. In p, the first clause tries l(X); suppose that eventually fails. The second clause calls r(X), which succeeds with X = 8, and then the cut is executed. At that moment:
The operational one-liner. Once you have executed the cut in a clause, failing afterwards takes you straight out to your parent — to whoever called this predicate. Goals to the right of the cut may still backtrack among themselves; but when failure reaches back past the cut, it leaves.
:- module(_,_). white_max(X,Y,X) :- X > Y, !. white_max(X,Y,Y) :- X =< Y.Remove the cut and nothing changes: the two conditions are mutually exclusive, so once X > Y has succeeded, the second clause is going to fail anyway. The cut merely says so in advance.
White cuts affect neither completeness nor correctness. Use them freely. In many cases the system introduces them for you; writing them yourself also documents that the clauses are exclusive.
:- module(_,_). address(X,Add) :- home_address(X,Add), !. address(X,Add) :- business_address(X,Add). home_address(john,pfluggerville). business_address(john,austin). business_address(mary,boston). member_normal(X,[X|_]). member_normal(X,[_|T]) :- member_normal(X,T). member_check(X,[X|_]) :- !. member_check(X,[_|T]) :- member_check(X,T).address/2 gives you one address, preferring the home one — the clause order becomes a priority order. Without the cut you would get both.
member_check/2 is the classic. member_normal/2 gives every occurrence, repeats included; often you only want to know whether something is there:
?- member_normal(1,[1,2,1,4]), write('found solution'), nl, fail.Expected answer: found solution found solution no
?- member_check(1,[1,2,1,4]), write('found solution'), nl, fail.Expected answer: found solution nofail/0 is a goal that always fails — and note that it is not impure; a = b would do the same job. Driving the search with write, nl, fail is the standard way to print every solution.
The first version finds 1 twice, because it occurs twice in the list. The cut in the base case of member_check/2 kills the alternative that the base case would otherwise have left behind, so the second occurrence is never reached.
Green cuts affect completeness but not correctness: every solution you get is right, you just do not get all of them — which is what you asked for. Necessary in many situations, but stay awake.
Here is the first red cut everyone writes:
:- module(_,_). red_max(X,Y,X) :- X > Y, !. % Red cut! red_max(_X,Y,Y). % the X =< Y test is *missing*The reasoning feels airtight, and it is the reasoning you would use in C: if X > Y we are done and we cut; otherwise we fell through, so X =< Y must hold, so why test it again?
?- red_max(5,2,M).Expected answer:
M = 5 ?
?- red_max(5,2,2).Expected answer:
yesThe maximum of 5 and 2 is 2. That is a wrong answer — not wrong of the system, which did exactly what was written, but wrong of the program.
Why does it happen? red_max(5,2,2) fails against the first head, because X cannot be both 5 and 2, so the cut is never executed. It then reaches the second clause, red_max(_X,Y,Y), which says nothing about the first argument and only requires the last two to be equal — and they are.
In pure logic programming you never have to think about this, because every clause means what it says independently. With cut you do.
:- module(_,_). red_max_delay_output(X,Y,M) :- X > Y, !, M = X. red_max_delay_output(_X,Y,Y). if_then_else_max(X,Y,M) :- ( X > Y -> M = X ; M = Y ).
?- red_max_delay_output(5,2,2).Expected answer:
noCorrect now. The rule of thumb: if you see a repeated variable in the head of a clause containing a cut, you are unifying before the cut, and that is where the surprises come from.
Tip two: use if-then-else, which is syntactic sugar over exactly this pattern:
?- if_then_else_max(5,2,2).Expected answer:
no
case(a,Y) :- !, Y = 1. case(b,Y) :- !, Y = 2. case(c,Y) :- !, Y = 3.But you would normally write it as a table:
case(a,1). case(b,2). case(c,3).which runs forwards and backwards. Write the cut version only when you actually want the case semantics.
:- module(_,_). days_in_year(Y,366) :- leap_year(Y), !. days_in_year(_Y,365). leap_year(Y) :- number(Y), 0 is Y mod 4.
?- days_in_year(4,D).Expected answer:
D = 366 ?So far so good. Now:
?- days_in_year(4,365).Expected answer:
yes
?- days_in_year(a,D).Expected answer:
D = 365 ?Year 4 apparently has both 366 days and 365, and the atom a is a year with 365 days.
Read the two clauses as logic, which is the whole point of the notation. The first says: a leap year has 366 days — true, no problem. The second says: any year has 365 days. Not "any other year" — any. So leap years satisfy both, and so does a. The program says exactly that, and the system is merely agreeing with you.
:- module(_,_). days_in_year_good(Y,D) :- leap_year(Y), !, D = 366. days_in_year_good(Y,D) :- standard_year(Y), D = 365. leap_year(Y) :- number(Y), 0 is Y mod 4. standard_year(Y) :- number(Y), R is Y mod 4, R \= 0.
?- days_in_year_good(4,365).Expected answer:
no
?- days_in_year_good(a,D).Expected answer:
noBoth clauses are now true statements standing on their own, and the cut has become white: remove it and the answers do not change.
days_in_year_delay_output(Y,D) :- leap_year(Y), !, D = 366. days_in_year_delay_output(_Y,365).This fixes days_in_year_delay_output(4,365), which now correctly fails. But days_in_year_delay_output(Y,366) with Y unbound answers no — also wrong, since leap years exist. leap_year/1 starts with number/1, which cannot generate, so the first clause fails without reaching the cut, and the second cannot match 366.
The underlying problem is unchanged: we are writing for one mode, "give the year, get the days".
days_in_year_moded(Y,_D) :- var(Y), !,
write('{ ERROR: the year must be bound. }\n'), abort.
days_in_year_moded(Y, D) :- leap_year(Y), !, D = 366.
days_in_year_moded(_Y,365).Now an unbound year is refused loudly instead of answered wrongly. days_in_year_moded(Y,366) prints the message and aborts.:- use_package([assertions,modes,rtchecks]). :- pred days_in_year_moded_assrt(+int,-int). days_in_year_moded_assrt(Y,D) :- leap_year(Y), !, D = 366. days_in_year_moded_assrt(_Y,365).Called with an unbound year, run-time checking reports
Requires in *calls*:
nonvar(_1)
But instead:
var(_1)which is the same protection, obtained by stating the intended mode rather than by programming around it — and the same assertion can also be checked statically, at compile time. :- module(_,_). split([],[],[]). split([X|L],[X|L1],L2) :- X >= 0, !, split(L,L1,L2). split([X|L],L1,[X|L2]) :- split(L,L1,L2). % should test X < 0
?- split([1,-1,3], L1, L2).Expected answer:
L1 = [1,3], L2 = [-1] ?Read the clauses as logic. The base case is fine either way round: the positives and the negatives of an empty list are both empty. The second clause says if X >= 0, it goes in the first list. The third says in all cases X goes in the second list — so strictly it needs its own X < 0 test.
With that test present the cut is green: it prunes a branch that would have failed anyway, and the answers are unchanged. Drop the test, as above, and you are relying on the cut for correctness — which works while you call it forwards with a bound list, and stops working the moment you do anything else.
Where the cut does and does not reach. This one is subtle and worth predicting before running:
:- module(_,_). k_1sol(j,k). k_1sol(X,Y) :- k(X), !, z(Y). k_1sol(l,m). k(a). k(b). z(1). z(2).What are all the solutions of k_1sol(X,Y)?
?- k_1sol(X,Y).Expected answer:
X = j, Y = k ? ; X = a, Y = 1 ? ; X = a, Y = 2 ? ; noFollow it. The first clause gives j,k with no cut involved. Ask for more, and the second clause runs: k(X) succeeds with X = a, and the cut executes — so k(b) disappears, and so does the third clause k_1sol(l,m). Then z(Y) runs and gives Y = 1.
Now ask for more again. z/1 still has an alternative — and the cut did not remove it, because z(Y) is to the right of the cut. So Y = 2 comes back. But there is no b,1 or b,2, because the cut did remove k(b).
One value of k, all values of z. That is a lot of control for one character.
That one predicate is what makes meta-programming possible: interpreters, shells, negation (next section), and higher order.
:- module(_,_).
p(X) :- call(X).
q(a,1).
q(b,2).
apply(P,Args) :-
G =.. [P|Args],
call(G).
maptuples(_P,[]).
maptuples(P,[Tuple|RTuples]) :-
apply(P,Tuple),
maptuples(P,RTuples).?- G = q(a,Y), p(G).Expected answer:
G = q(a,1), Y = 1 ?p/1 received a term and executed it as a goal.
Here is what you would like to write. Somebody gives you a value, X = a, and somebody else gives you a predicate symbol, Y = p. You want to call Y(X) — check whether a has property p, where the property arrives as data.
You cannot write that in standard Prolog. Y(X) is higher-order syntax and will not even parse. (Ciao, with the hiord package, does accept it — but that is an extension, not standard Prolog.)
Standard Prolog can still do it, using univ. Remember that univ spills a term into a list with the functor at the front — and that a list can contain variables. So build the term and then call it:
:- module(_,_). :- use_module(library(lists)).
?- Y = p, P =.. [Y,a].Expected answer:
P = p(a), Y = p ?P is now p(a), which has the right shape to be called. That is application of a predicate passed as data — we have just invented apply.
Now generalize. Suppose the symbol is member and the arguments are X and [1,2,3]. Writing them out works:
?- F = member, Args = [X,[1,2,3]], P =.. [F|Args].Expected answer:
Args = [X,[1,2,3]], F = member, P = member(X,[1,2,3]) ?But notice how easy the near-miss is. Put the argument list in as a single element rather than splicing it, and you get a predicate of arity one:
?- F = member, L = [X,[1,2,3]], P =.. [F,L].Expected answer:
F = member, L = [X,[1,2,3]], P = member([X,[1,2,3]]) ?member/1, with a two-element list as its only argument. Not what we wanted, and almost a typo.
?- F = append, Args = [X,Y,Z], P =.. [F|Args], call(P).Expected answer:
Args = [[],Y,Y], F = append, P = append([],Y,Y), X = [], Z = Y ?Three arguments this time, and it worked without changing anything — because Args can be any length. Wrap those two goals in a predicate and you have
apply(F,Args) :- G =.. [F|Args], call(G).
There is another route, incidentally, called a Warren transformation, and the call/n family of the next slide is a third. And once you have any of them, map, fold and the rest are ordinary library predicates.
Combining call/1 with univ this way gives us apply/2: assemble the goal out of the predicate name and the argument list, then call it.
?- P = q, apply(P,[a,Y]).Expected answer:
P = q, Y = 1 ?The predicate arrived in a variable. And with apply/2 in hand, a map over a list of argument tuples is three lines:
?- maptuples(q,[[a,X],[b,Y]]).Expected answer:
X = 1, Y = 2 ?
:- module(_,_,[hiord]). :- use_module(library(lists)). :- use_module(library(hiordlib)). q(a,1). q(b,2). plus2(X,Y,Z) :- number(X), number(Y), Z is X + Y. plus2(X,Y,Z) :- number(X), number(Z), Y is Z - X. plus2(X,Y,Z) :- number(Y), number(Z), X is Z - Y.
?- P = q, call(P,X,Y).Expected answer:
P = q, X = a, Y = 1 ?
?- P = q(a), call(P,Y).Expected answer:
P = q(a), Y = 1 ?Both spellings work: hand over the bare predicate name and supply both arguments, or hand over a partially applied term and supply the rest. It works for library predicates just as well:
?- P = append, call(P,[1,2],[3,4],Y).Expected answer:
P = append, Y = [1,2,3,4] ?
?- P = append(X,Y), call(P,[1,2,3,4]).Expected answer:
P = append([],[1,2,3,4]), X = [], Y = [1,2,3,4] ?— and on backtracking gives all the splittings, because the predicate passed in is still the relational append/3.
?- maplist(plus2, [1,2,3], [4,5,6], R).Expected answer:
R = [5,7,9] ?
?- maplist(plus2, [1,2,3], L2, [5,7,9]).Expected answer:
L2 = [4,5,6] ?maplist ran backwards, because the predicate it was given runs backwards. Higher-order predicates inherit the reversibility of what you pass them — something a functional map cannot do, since a function only goes one way.
maplist with append/3 and shared variables gets quietly spectacular:
?- maplist(append, [ [a], X, X ],
[ [c,d], [e,f], Y ],
[ X, Y, Z] ).Expected answer: X = [a,c,d], Y = [a,c,d,e,f], Z = [a,c,d,a,c,d,e,f] ?Three appends whose inputs and outputs are wired to each other — solved as a system of equations, not executed in the order written.
:- module(_,_,[hiord]). :- use_module(library(lists)). :- use_module(library(hiordlib)). p(a,1). p(b,2).
?- P = p, P(X,Y).Expected answer:
P = p, X = a, Y = 1 ?
?- P = append, P([1,2],[3,4],Y).Expected answer:
P = append, Y = [1,2,3,4] ?Anonymous predicates — read the _ as a lambda:
?- P = { _(X,Y) :- Y is X+1 }, P(3,R).Expected answer: P = {''(X,Y):-Y is X+1},
R = 4 ?And they can be passed straight to maplist:?- maplist({ _(X,Y) :- Y is X+1 }, [1,2,3], R).Expected answer: R = [2,3,4] ?
findall(Term, Goal, ListResults): ListResults is the list of all instances of Term for which Goal succeeds. If there are none, it is []. For it to terminate, the number of solutions must of course be finite and enumerable in finite time.
:- module(_,_). likes(bill, cider). likes(dick, beer). likes(tom, beer). likes(tom, cider). likes(harry, beer). likes(jan, cider).
?- findall(X, likes(X,Y), S).Expected answer:
S = [bill,dick,tom,tom,harry,jan] ?tom appears twice, because he satisfies the goal twice — findall/3 collects solutions, not distinct values.
The first argument is a pattern, and it does more than name a variable. It says what to build out of each solution:
?- findall(l(X), likes(X,Y), S).Expected answer:
S = [l(bill),l(dick),l(tom),l(tom),l(harry),l(jan)] ?Whatever term you write there is instantiated once per solution and collected. That is what makes these predicates as useful as they are.
And an empty result is an empty list, not a failure:
?- findall(X, likes(X,water), S).Expected answer:
S = [] ?
The ordering setof/3 uses is the standard order of terms, the same @< from slide 26 — which is what makes it able to sort any answers, not just numbers or atoms.
The backtracking behaviour is the interesting one, and it is the part people find strange at first.
Think about what you are asking for in setof(X, likes(X,Y), S). Y appears in the goal but not in the pattern, so it is a free variable — and it is genuinely unclear what you want. All the X for every Y lumped together? Or all the X for each Y separately?
So setof/3 answers once per drink:
?- setof(X, likes(X,Y), S). S = [dick,harry,tom], Y = beer ? ; S = [bill,jan,tom], Y = cider ? ; noThat is a group-by, obtained for free. Wrap it in another aggregation and you get the whole grouping in one term:
?- findall(t(Y,S), setof(X,likes(X,Y),S), Pairs).Expected answer:
Pairs = [t(beer,[dick,harry,tom]),t(cider,[bill,jan,tom])] ?If that is not what you wanted, say so: Y^Goal is existential quantification, read "there exists a Y such that …". Quantify Y away and you get everybody, once each:
?- setof(X, Y^likes(X,Y), S).Expected answer:
S = [bill,dick,harry,jan,tom] ?Note that tom is not repeated, even though he appears twice in the database — setof/3 always sorts and removes duplicates.
And with no solutions at all, setof/3 fails where findall/3 returned []:
?- setof(X, likes(X,water), S).Expected answer:
no
:- module(_,_). subset([],[]). subset([X|Xs],[X|Ys]) :- subset(Xs,Ys). subset([_|Xs],Ys) :- subset(Xs,Ys). powerset(Set,Pset) :- setof(X,subset(Set,X),Pset).
?- powerset([1,2,3],Pset).Expected answer:
Pset = [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]] ?subset/2 generates the subsets one at a time on backtracking; setof/3 gathers and sorts them. Eight of them, as it should be.
:- module(_,_).
not( Goal) :- call(Goal), !, fail.
not(_Goal).
unmarried_student(X) :-
\+ married(X),
student(X).
student(joe).
married(john).Read the two clauses. If Goal succeeds, we execute the cut — killing the second clause — and then fail: so not(Goal) fails, and because the cut removed every alternative before it, there is nowhere left to backtrack to. If Goal fails, the first clause fails before the cut, and the second clause succeeds unconditionally. Negation, in two lines, out of call/1, ! and fail/0.The standard spelling is the prefix operator \+/1, so you write \+ member(c,[a,k,l]).
?- X = 1, \+ X = 2.Expected answer:
X = 1 ?
?- \+ X = 1.Expected answer:
nono — because a free X can be unified with 1, so the goal being negated succeeds.
Here is the difference, on a concrete question — does this list start with 1?
:- module(_,_). :- use_module(library(lists)).
?- L = [X,2,3], append([1],R,L).Expected answer:
L = [1,2,3], R = [2,3], X = 1 ?It answered yes — and changed the list: X got bound to 1. Now with the double negation:
?- L = [X,2,3], \+ \+ append([1],R,L).Expected answer:
L = [X,2,3] ?Same answer to the question, and L is untouched. Note also that the cut inside \+/1 means you get one yes-or-no answer and no choice points, where the direct call would have left them.
Termination. \+ Goal terminates exactly when the search for Goal reaches a success node before an infinite branch. If Goal loops, so does its negation.
?- unmarried_student(joe).Expected answer:
yes
?- unmarried_student(X).Expected answer:
noJoe is an unmarried student — the first query says so. The second says no unmarried student exists. Both cannot be right.
What happened: \+ married(X) is called with X still unbound. It asks "is there any X such that married(X)?" — and there is, john — so married(X) succeeds, and the negation fails. The whole conjunction dies before student(X) is ever reached.
The deeper reason is the one above: negation does not instantiate. Even conceptually there is nothing for it to return — it does not know of any X for which the negation holds, only that the negation does not hold in general.
Asked about specific people it behaves perfectly: unmarried_student(joe) is yes, and unmarried_student(john) is no — for two independent reasons, since John is married and is not a student in this database. It is only generation that breaks.
Negation as failure is only correct for ground goals, and it is the programmer's responsibility to ensure that. The most frequent fix is simply to put the generator first, so the variable is bound by the time the negation runs:
unmarried_student(X) :- student(X), \+ married(X).Now student(X) generates joe, and only then is married(joe) negated. unmarried_student(X) answers X = joe, and nobody else. Generate, then test — the same lesson as the goal-ordering slides in the previous deck, with sharper consequences.
not_(G) :-
ground(G), !,
\+ G.
not_(G) :-
write('ERROR: Non-ground goal in negation: '), write(G), nl,
abort.Called through this, unmarried_student_with_check(X) reports ERROR: Non-ground goal in negation: married(_1223) and aborts, instead of quietly answering no.Or state it as an assertion and let the system check it:
:- use_package([assertions,modes,rtchecks]). :- pred not__(G) : ground(G). not__(G) :- \+ G.The : field is the call condition: G must be ground when called. With run-time checking on, a violation is reported automatically; the same assertion can also be verified statically.
once(G) :- call(G), !.once/1 gives the first solution of G and no more. ?- once(member(X,[1,2,3])). answers X = 1 and offers no alternatives.
The example is ground/1 itself — a term is ground if it contains no variables, so we fail as soon as we find one:
:- module(_,_).
ground_(Term):- var(Term), !, fail.
ground_(Term):-
nonvar(Term),
functor(Term,_F,N),
ground_args(N,Term).
ground_args(0,_T). %% all subterms traversed
ground_args(N,T):-
N>0,
arg(N,T,Arg),
ground_(Arg),
N1 is N-1,
ground_args(N1,T).?- ground_(f(a,g(b))).Expected answer:
yes
?- ground_(f(a,g(X))).Expected answer:
noThe first clause is the cut-fail: if the term is a variable, cut away the second clause and fail — committing to "no" rather than letting the structure walk continue. The rest is the same descent through functor/3 and arg/3 as in subterm/2.
Useful, and dangerous for the same reason all cuts are: the failure is committed, so there is no backtracking into an alternative reading.
There are cases where assertion and retraction can be logically justified:
Requirements differ between implementations. Typically a predicate that does not already appear in the program can always be asserted; otherwise it must be declared :- dynamic.
:- module(_,_). related(3,4). :- dynamic related/2. relate_numbers(X,Y) :- assert(related(X,Y)). unrelate_numbers(X,Y) :- retract(related(X,Y)).
?- related(1,2).Expected answer:
no
?- relate_numbers(1,2), related(1,2).Expected answer:
yes
?- relate_numbers(1,2), unrelate_numbers(1,2), related(1,2).Expected answer:
noAnd abolish(related/2) removes the predicate altogether — after which calling it is an existence error, not a failure, because the predicate no longer exists.
Rules can be asserted too, not just facts. Since a clause is a term, you build the term and assert it:
:- module(_,_).
:- dynamic app/3.
define_app :-
assert( app([], X, X) ),
assert( ( app([X|Y], Z, [X|W]) :- app(Y,Z,W) ) ).
erase_app :-
abolish(app/3).?- define_app, app([1,2],[3,4],Y).Expected answer:
Y = [1,2,3,4] ?A complete append/3, written into the program at run time — and note the extra parentheses around the rule, needed because :- has priority 1200, well above the comma's 1000.
?- C = (related(X,Y) :- X =< Y), assert(C). C = (related(X,Y):-X=<Y) ? ?- related(1,2). yes ?- related(2,1). no
retractall/1 removes every clause of a dynamic predicate at once, which is the usual way of starting over.
:- module(_,_).
% 1) Direct definition:
fib(0, 0).
fib(1, 1).
fib(N, F) :-
N > 1,
N1 is N - 1,
N2 is N - 2,
fib(N1, F1),
fib(N2, F2),
F is F1 + F2.
% 2) Version that records 'lemmas' (things already proved):
:- dynamic lemma_fib/2.
lemma_fib(0, 0).
lemma_fib(1, 1).
lfib(N, F) :-
lemma_fib(N, F), % already known? take it
!.
lfib(N, F) :-
N > 1,
N1 is N - 1,
N2 is N - 2,
lfib(N1, F1),
lfib(N2, F2),
F is F1 + F2,
assert(lemma_fib(N, F)). % ...and remember itYou can already see why the first version is bad: computing fib(N-1) and fib(N-2) separately throws away all the work the first one did, because fib(N-1) computed fib(N-2) along the way.Print a line on every call and ask for position 4 and you see it: the calls are 3, 2, 1, 0, 1, 2, 1, 0 — position 3 computed once, position 2 twice, position 1 three times. Exponential: each new number recomputes the whole tree beneath it.
The lemma version differs in exactly two places — look it up if we already have it, and record what we just computed. The first time you call it, it makes about the same number of calls, in fact slightly fewer, because as soon as it reaches a position it has already computed it stops descending. And the second call is where it pays: asking for position 5 after position 4 needs only one addition, because 4 and 3 are both already known.
What that buys, measured in this Ciao on the machine these notes were written on:
?- statistics(walltime,_), fib(27,Y), statistics(walltime,[_,T]). T = 36.843, Y = 196418 ? ?- statistics(walltime,_), lfib(27,Y), statistics(walltime,[_,T]). T = 0.028, Y = 196418 ?Thirty-seven milliseconds against twenty-eight microseconds — three orders of magnitude, and the gap widens with N. And lfib/2 goes far beyond where the naive one can follow:
?- lfib(200,Y).Expected answer:
Y = 280571172992510140037611932413038677189525 ?
The slide's closing quotation is the right one: those who cannot remember the past are condemned to repeat it.
And there is a practical trap. The lemmas survive: if you experiment at the top level and start getting results you cannot explain, remember that a previous run's assertions may still be there. Reload the module, or retractall(lemma_fib(_,_)) and re-assert the base cases, before you time anything.
You cannot call it with the head completely unbound — that would be asking to enumerate every clause in the program — so you give it a head pattern and it enumerates the matching clauses on backtracking. For lappend/3 it returns the two you would expect:
cl(lappend([],A,A), true) cl(lappend([B|C],A,[B|D]), lappend(C,A,D))
But it does not execute anything. The difference shows up on rules: if you assert the fact related(0,0) then clause(related(X,Y),B) gives you X = 0, Y = 0; if you assert the rule related(X,Y) :- X = 0, Y = 0 — the same meaning — then clause/2 gives you back the body unexecuted, and X and Y stay free. For a fact, unifying and executing happen to be the same thing; for a rule they are not.
With it you can write an interpreter for Prolog, in Prolog, in three lines:
:- module(_,_,[]). :- use_package(dynamic_clauses). :- dynamic lappend/3. lappend([],X,X). lappend([X|Y],Z,[X|W]) :- lappend(Y,Z,W). % The 'vanilla' meta-interpreter: solve( true ) :- !. solve( (A,B) ) :- !, solve(A), solve(B). solve( A ) :- clause(A,B), solve(B).Read the three clauses: true is solved, so we are finished; a pair (A,B) is solved by solving A and then B — and it is a pair because that is how clause bodies are built, conjunctions nested to the right with ,/2; and anything else is solved by finding a clause whose head it matches and solving that clause's body.
?- solve(lappend([1,2],[3,4],L)).Expected answer:
L = [1,2,3,4] ?
?- solve(lappend(X,Y,[1,2,3,4])).Expected answer:
X = [], Y = [1,2,3,4] ? ; X = [1], Y = [2,3,4] ? ; X = [1,2], Y = [3,4] ? ; X = [1,2,3], Y = [4] ? ; X = [1,2,3,4], Y = [] ? ; no
Tracing is the easiest. Put a write/1 and nl/0 in the third clause and every goal the interpreter attempts is printed — without modifying the program being interpreted at all. Put them after the recursive call instead and you see the state on the way back out, which is how the answer list gets built. That is already a usable debugger, in two extra goals.
Add one argument instead and you have a step counter:
:- module(_,_,[]). :- use_package(dynamic_clauses). :- dynamic lappend/3. lappend([],X,X). lappend([X|Y],Z,[X|W]) :- lappend(Y,Z,W). csolve( true, 0) :- !. csolve( (A,B), N) :- !, csolve(A,NA), csolve(B,NB), N is NA+NB. csolve( A, N) :- clause(A,B), csolve(B,N1), N is N1+1.
?- csolve(lappend([1,2],[3,4],L),N).Expected answer:
L = [1,2,3,4], N = 3 ?Three steps. Now the question the example file asks: what does the cost of append/3 depend on?
?- csolve(lappend([1,2,a,b],[3,4],L),N).Expected answer:
L = [1,2,a,b,3,4], N = 5 ?
?- csolve(lappend([1,2],[3,4,a,b],L),N).Expected answer:
L = [1,2,3,4,a,b], N = 3 ?Lengthening the first list costs steps; lengthening the second costs nothing. append/3 recurses on its first argument only, and the cost is the length of that list plus one — measured here, not argued for.
Trace the three steps against the program and they line up exactly: one for each element of [1,2], and one more where the base case lappend([],X,X) matches. This is a genuinely useful way to find the complexity of a predicate: you get a number rather than an argument.
Start with why ordinary append/3 is expensive. You have a pointer X to [1,2,3], ending in [], and a pointer Y to [4,5]. append(X,Y,Z) builds the result by taking the head off the first list and putting it at the front of the result, over and over — that is, it copies the first list. Only when it reaches the base case, append([],L,L), does it make the last cell point at Y. So the second list is not copied; the first one is, entirely. That is where the cost comes from.
What you would rather do is attach something at the end of the first list. And you can — if you leave the tail as a free variable and keep a pointer to it. Then appending is a single unification: point the tail at the second list. Constant time.
[1,2,3] is a closed list: it ends in [], and you cannot add to the end without walking to it first — which is exactly why append/3 costs the length of its first argument.
An incomplete (or open) list ends in a variable: [1,2,3|T]. Now you can add at the end, by instantiating T. The idea of a difference list is to carry that variable around with the list, so you always have a pointer to the end:
:- module(_,_). % A pseudo-type for difference lists: dlist(X-X). dlist([_|DL]-X) :- dlist(DL-X). % Appending difference lists, in constant time: append_dl(B1-E1, E1-E2, B1-E2).The -/2 is just a functor pairing the list with its own tail; nothing arithmetic is going on. It is traditional, and it is where the name "difference list" comes from — you are carrying the list together with what must be subtracted from it.
Which is what the pseudo-type says:
dlist(X-Y) :- var(X), !, X == Y. dlist([_|DL]-X) :- dlist(DL-X).Note the ! and the ==/2 — this is not pure; it is a Prolog program, and it could not have been written in the previous deck.
The explicit version of the append reads more clearly:
append_dl(B1-E1,B2-E2,B3-E3) :- B3=B1, E3=E2, B2=E1.The result starts where the first list starts, ends where the second ends, and the second list is plugged into the hole at the end of the first. Written compactly, all three unifications happen in the head — which is why append_dl/3 is a fact, and runs in constant time regardless of length.
And the same trick generalizes. These are really incomplete data structures, not merely incomplete lists: leave variables at the ends of any structure and you can extend it there. Difference trees have variables at the leaves, and from them you get dictionaries and queues. It is one of the great tricks of logic programming — and specifically of Prolog.
?- L1 = [1,2,3|X], L2 = [4,5|Y], L2 = X.Expected answer:
L1 = [1,2,3,4,5|Y], L2 = [4,5|Y], X = [4,5|Y] ?L1 is now [1,2,3,4,5|Y], and one unification did it.
Now through the predicate:
?- append_dl([1,2,3|X]-X, [4,5|Y]-Y, L).Expected answer:
L = [1,2,3,4,5|Y]-Y, X = [4,5|Y] ?
And it runs backwards, because it is a fact and everything is unification. Given the result and the second list, it recovers the first:
?- append_dl(L-X, [4,5|Y]-Y, [1,2,3,4,5|Z]-Z).Expected answer:
L = [1,2,3,4,5|Y], X = [4,5|Y], Z = Y ?
:- module(_,_).
:- use_module(library(lists)).
qsort([],[]).
qsort([X|L],S) :- % take the first element of the list in X
partition(L,X,LS,LB), % LS: elements of L < X; LB: elements >= X
qsort(LS,LSS), % LSS is LS sorted
qsort(LB,LBS), % LBS is LB sorted
append(LSS,[X|LBS],S). % put the pivot between the two sorted halves
partition([],_P,[],[]).
partition([E|R],P,[E|Smalls],Bigs) :-
E < P,
partition(R,P,Smalls,Bigs).
partition([E|R],P,Smalls,[E|Bigs]) :-
E >= P,
partition(R,P,Smalls,Bigs).?- qsort([5,2,1,3,7,6], SL).Expected answer:
SL = [1,2,3,5,6,7] ?Trace the top call: X = 5, L = [2,1,3,7,6]; partition gives LS = [2,1,3] and LB = [7,6]; sorting those gives [1,2,3] and [6,7]; and the final call is append([1,2,3],[5,6,7],S).
That append/3 is the waste. It walks the whole left half again, at every level of the recursion.
Consider the input list first. We have to walk down it anyway — there is no way to sort a set without looking at every element — and we never append to it. So it can stay an ordinary list.
Now the result. That is where the appends are, because we have the small ones and the big ones and have to join them. So that is what becomes a difference list.
And a pleasant consequence: partition/4 does not change at all. It takes a normal list and a pivot and produces two normal lists — which is exactly what the recursive calls want in their first argument.
:- module(_,_).
qsort_dl1(L,SL) :- % L = [5,2,1,3,7,6]
qsort_dl1_(L,SL-SLE), % SL = [1,2,3,5,6,7|SLE]
SLE = []. % SL = [1,2,3,5,6,7]
qsort_dl1_([],SLE-SLE).
qsort_dl1_([X|L],SL-SLE) :- % X = 5, L = [2,1,3,7,6]
partition(L,X,S,B), % S = [2,1,3], B = [7,6]
qsort_dl1_(S,SS-SSE), % SS = [1,2,3|SSE]
qsort_dl1_(B,BS-BSE), % BS = [6,7|BSE]
SSE = [X|BS], % SSE = [5,6,7|BSE]
SL = SS, % SL = [1,2,3,5,6,7|BSE]
SLE = BSE. % SL = [1,2,3,5,6,7|SLE]
partition([],_P,[],[]).
partition([E|R],P,[E|Smalls],Bigs) :- E < P, partition(R,P,Smalls,Bigs).
partition([E|R],P,Smalls,[E|Bigs]) :- E >= P, partition(R,P,Smalls,Bigs).?- qsort_dl1([5,2,1,3,7,6], SL).Expected answer:
SL = [1,2,3,5,6,7] ?Reason about it recursively, which means believing the recursion works. We give it [5,2,1,3,7,6]; the pivot is 5, and partition/4 gives S = [2,1,3] and B = [7,6] as ordinary lists. Sort those: the first call returns [1,2,3] as a difference list, SS to SSE; the second returns [6,7], BS to BSE.
Now comes the point where the old version did the append/3 — and here is the magic. What came back are difference lists, so we have a handle on the end of each. Three unifications suffice:
No traversal, no append/3. And the top-level qsort_dl1/2 then does one last thing for the benefit of a caller who knows nothing about difference lists: SLE = [] closes the open tail, converting the difference list back into an ordinary one.
Version 2 — the same program, with the unifications moved into the head and the arguments, where they belong:
:- module(_,_).
qsort_dl2(L,SL) :-
qsort_dl2_(L,SL-[]).
qsort_dl2_([],SLE-SLE).
qsort_dl2_([X|L],SL-SLE) :-
partition(L,X,S,B),
qsort_dl2_(S,SL-[X|BS]),
qsort_dl2_(B,BS-SLE).
partition([],_P,[],[]).
partition([E|R],P,[E|Smalls],Bigs) :- E < P, partition(R,P,Smalls,Bigs).
partition([E|R],P,Smalls,[E|Bigs]) :- E >= P, partition(R,P,Smalls,Bigs).?- qsort_dl2([5,2,1,3,7,6], SL).Expected answer:
SL = [1,2,3,5,6,7] ?Six lines of explicit =/2 have vanished into two argument positions. qsort_dl2_(S,SL-[X|BS]) says in one term: sort S into the front of SL, and let its tail be the pivot followed by whatever BS turns out to be.
Version 3 — drop the -/2 functor and use two arguments instead:
:- module(_,_).
qsort_dl(L,SL) :-
qsort_dl_(L,SL,[]).
qsort_dl_([],SLE,SLE).
qsort_dl_([X|L],SL,SLE) :-
partition(L,X,S,B),
qsort_dl_(S,SL,[X|BS]),
qsort_dl_(B,BS,SLE).
partition([],_P,[],[]).
partition([E|R],P,[E|Smalls],Bigs) :- E < P, partition(R,P,Smalls,Bigs).
partition([E|R],P,Smalls,[E|Bigs]) :- E >= P, partition(R,P,Smalls,Bigs).?- qsort_dl([5,2,1,3,7,6], SL).Expected answer:
SL = [1,2,3,5,6,7] ?The -/2 was only ever notation; two plain arguments carry the same pair, and one term construction per call is saved. In fact the - version is slightly more expensive, since it builds a structure that nothing needs.
Remember this shape — a pair of arguments, one for the list and one for its tail — because it is exactly what the grammar section is about.
The goal of this section is to parse the plane flies — and to arrive at a notation in which the program is the grammar. We get there in four steps, each one a mechanical improvement on the last.
:- module(_,_).
:- use_module(library(lists)).
myphrase(P) :-
append(A,T1,P), article(A),
append(S1,T2,T1), spaces(S1), % spaces must follow
append(N,T3,T2), noun(N), % a noun must follow
append(S2,V,T3), spaces(S2), % spaces must follow
verb(V). % must end with a verb
article([a]).
article([t,h,e]).
spaces([' ']).
spaces([' ' | Y]) :- spaces(Y).
noun([c,a,r]).
noun([p,l,a,n,e]).
verb([f,l,i,e,s]).
verb([d,r,i,v,e,s]).?- myphrase([t,h,e,' ',p,l,a,n,e,' ',f,l,i,e,s]).Expected answer:
yesA grammar is a description of the syntax of the phrases we want to recognize — formally similar to the automata of the previous deck, but richer and higher-level. Words are represented as lists of letters: a is [a], "the" is [t,h,e]. Spaces are one or more, which is an ordinary two-clause recursion.
How does it work? article(A) picks an article — a or the — and then append(A,T1,P), run backwards, asks whether the phrase starts with it, handing back the rest as T1. Then the same question is asked of T1, and so on down the phrase. append/3 is being used to subtract a piece off the front.
?- myphrase([' ',t,h,e,' ',p,l,a,n,e,' ',f,l,i,e,s]).Expected answer:
noA leading space is rejected — nothing in the grammar allows one. But extra spaces inside are fine, because spaces/1 is recursive:
?- myphrase([a,' ',' ',p,l,a,n,e,' ',d,r,i,v,e,s]).Expected answer:
yes
Every alternative in the grammar is a clause, and every backtrack is Prolog doing what it does anyway.
It works, and it is generate and test: append/3 proposes every possible split of the list, and article/1 and the rest reject the bad ones. Every split of every remaining tail — the cost is appalling, and there are four append/3 calls.
:- module(_,_).
myphrase(P,C) :-
article(P,CA), spaces(CA,CS1),
noun(CS1,CN), spaces(CN,CS2),
verb(CS2,C).
article([a | T],T).
article([t,h,e | T],T).
spaces([' ' | T],T).
spaces([' ' | Y],T) :- spaces(Y,T).
noun([c,a,r | T],T).
noun([p,l,a,n,e | T],T).
verb([f,l,i,e,s | T],T).
verb([d,r,i,v,e,s| T],T).?- myphrase([t,h,e,' ',p,l,a,n,e,' ',f,l,i,e,s],[]).Expected answer:
yesEvery append/3 is gone. article([t,h,e|T],T) says if the input starts with the, the remainder is T — a fact, matched by head unification alone, in constant time. The [] in the query says "and there must be nothing left over".
This is the difference-list pair from the quicksort section, doing a completely different job. The chaining of the variables — P to CA to CS1 to CN to CS2 to C — is the sequencing of the grammar.
:- module(_,_).
myphrase(X,C) :-
article(X,CA), spaces(CA,CS1),
noun(CS1,CN), spaces(CN,CS2),
verb(CS2,C).
article("a" || T, T).
article("the" || T, T).
spaces(" " || T, T).
spaces(" " || Y, T) :- spaces(Y, T).
noun("car" || T, T).
noun("plane" || T, T).
verb("flies" || T, T).
verb("drives" || T, T).?- myphrase("the plane flies",[]).Expected answer: yes
?- myphrase("the plane drove",[]).Expected answer: noNothing changed but the notation. "the" || T is exactly [t,h,e|T] with the characters as codes.
So let the system add them. That transformation is what a definite clause grammar is: --> is a macro that expands into the threaded two-argument version.
:- module(_,_). :- use_package(dcg). myphrase --> article, spaces, noun, spaces, verb. article --> "a". article --> "the". spaces --> " ". spaces --> " ", spaces. noun --> "car". noun --> "plane". verb --> "flies". verb --> "drives".That is a grammar. Read it aloud: a phrase is an article, spaces, a noun, spaces and a verb. The --> clauses are translated into exactly the two-argument predicates of the previous slide — myphrase --> article, spaces, ... becomes
myphrase(X,CV) :- article(X,CA), spaces(CA,CS1), noun(CS1,CN),
spaces(CN,CS2), verb(CS2,CV).and article --> "a". becomes article("a" || X, X). Nothing new is happening; the arguments are merely implicit.?- myphrase("the plane flies",[]).Expected answer: yesNote that the query passes two arguments — because what is really being called is the expanded version; the arrow put them there.
You can also use the phrase/2 built-in, which supplies the [] for you:
?- phrase(myphrase,"the plane flies").Expected answer:
yes
?- myphrase(S,[]).Expected answer:
S = "a car flies" ?The same six lines that recognize sentences also produce them, on backtracking. A parser generator in another language gives you a recognizer; here you get the relation.
(Under depth-first search the enumeration will get stuck in the recursive spaces rule. Add :- use_package(sr/bfall). and ask again to see the whole language come out.)
Grammar rules can carry extra arguments, and can call ordinary Prolog between braces { ... } — anything in braces is straight Prolog and does not get the hidden arguments added. And note where the extra arguments go: the two hidden ones are always the last two, so yours go in front of them.
Here is the same grammar, counting the characters it consumes:
:- module(_,_).
:- use_package(dcg).
myphrase(N) --> article(AC), spaces(S1), noun(NC), spaces(S2),
verb(VC), { N is AC + S1 + NC + S2 + VC }.
article(1) --> "a".
article(3) --> "the".
spaces(1) --> " ".
spaces(N) --> " ", spaces(N1), { N is N1+1 }.
noun(3) --> "car".
noun(5) --> "plane".
verb(5) --> "flies".
verb(6) --> "drives".?- myphrase(N,"the plane flies",[]).Expected answer:
N = 15 ?
?- phrase(myphrase(N),"the plane flies").Expected answer:
N = 15 ?Fifteen characters: 3 + 1 + 5 + 1 + 5.
Read the rules. article(1) --> "a". says an article may be a, and it has one character. The spaces rule is the pretty one: one space has one character; and a space followed by more spaces, which have N1 characters, has N1 + 1 — with { N is N1+1 } being plain Prolog dropped into the middle of a grammar rule. So spaces//1 really has three arguments: the two hidden ones doing the difference-list threading, and the extra one carrying the count.
The counts travel in that extra argument, and a final { ... } adds them up. Which is exactly how you would build an abstract syntax tree as you parse, or compute a value, or check a context condition — the grammar and the semantic actions live in the same notation.
Repeat loops and failure-driven loops. The pattern is worth recognizing, because it is what imperative Prolog looks like:
main(_) :- repeat, read(X), process(X). process(end). process(X) :- display(X), nl, fail.repeat/0 succeeds infinitely often on backtracking, and fail/0 drives the loop round again — so this reads a term, prints it, fails back into repeat, and reads the next, until end arrives and the first clause of process/1 stops the loop. It is a while loop built out of backtracking.
Also: exception handling; extending the syntax beyond operators with term expansions and macros, which is what a package is; delay declarations and concurrency; the operating system interface, sockets; foreign language interfaces such as C; and a great many more built-ins.
| Arrays | Assoc | Attributes | Heaps |
| Lists | Term Utilities | Ordset | Queues |
| Random | System Utilities | Tree | UGraphs |
| WGraphs | Sockets | Linda/Distribution | Persistent DB |
| CLPB | CLPQR | CLPFD | Objects |
| GCLA | TclTk | Tracing | Chars I/O |
| Runtime Utilities | Timeout | Xrefs | WWW |
| Java Interface | ... | ... | ... |
Other execution rules. Breadth-first, iterative deepening, random; tabling; CASP (negation with multiple models); Andorra ("determinate-first") execution; fuzzy Prolog. You have used one of these all along — sr/bfall — so the mechanism is already familiar.
Interfaces to other languages and systems. C, Java, JavaScript, Python, LLVM; an SQL database interface and persistent predicates; web, HTML, XML and CGI programming (PiLLoW), HTTP connectivity, JSON, compilation to JavaScript; interfaces to solvers (PPL, Mathematica, MiniSAT, Z3, Yices); Graphviz and daVinci; Electron, wxWidgets, Tcl/Tk, VRML.
Syntactic and semantic extensions. Functional notation and higher order — both of which appeared in these notes; terms with named arguments (records and feature terms); multiple argument indexing; the script interpreter; active modules for high-level distributed execution; concurrency and multithreading; attributed variables; object-oriented programming.
Constraint programming. Rationals, reals, finite domains; CHR (constraint handling rules), GeCode. This is the subject of the last part of the course, and it is where the arithmetic we lost on slide 13 comes back — declarative and efficient.
Assertions, which appeared here twice as a way of stating what a predicate expects: regular types, modes, determinacy and other properties; run-time checking; assertion-based unit tests and automatic test case generation; and compile-time property inference and assertion checking with CiaoPP.
Additional programming support. Automatic documentation (LPdoc — which produced the document you are reading); partial evaluation, optimization and parallelization (CiaoPP).
Sometimes that is exactly the right trade. The next part of the course shows that for arithmetic, at least, it was never necessary.