Contents:
Aims |
To study input and output in Prolog, including:
Also:
|
References | Bratko, I. Programming in Prolog for Artificial Intelligence,
3rd Edition, Addison-Wesley, 2000, chapter 6; SWI Prolog documentation |
Keywords |
write ,
nl ,
read ,
see , seen , tell , told ,
tab ,
putc , getc ,
atom_chars ,
consult .
|
This isn't enough for writing serious applications in Prolog. We might want:
The built-in predicates described in this section make these things possible.
write
The built-in write(C)
prints whatever
is bound to C
. For example,
?- write(likes(mary, pizza)). likes(mary, pizza) ?- write(23). 23 ?- write('apple'). apple ?- write("apple"). [97, 112, 112, 108, 101]
write
continuedlikes(mary, fish). likes(john, lamb).Then
?- likes(mary, X), likes(john, Y), write(X), write(Y). fishlamb X = fish Y = lamb
nl
likes(mary, fish). likes(john, lamb).Then
?- likes(mary, X), likes(john, Y), write(X), nl, write(Y). fish lamb X = fish Y = lamb
To get rid of the bindings you can do the following:
% cat stuff.pl likes(mary, fish). likes(john, lamb). thing :- likes(mary, X), likes(john, Y), write(X), nl, write(Y), nl. % prolog -s stuff.pl ?- thing. fish lamb true.
read
One can also read terms typed on the keyboard:
?- read(X). |: likes(fido, biscuits). X = likes(fido, biscuits)
|:
"
to signal that it is waiting for keyboard input.
[You may also see a prompt "|
"if you type a prolog query
into the interpreter spread over more than one line.
read
,
the term must be terminated with a ".
". If you leave this
out, you'll get another |:
prompt.
?- read(X). |: 23. X = 23 ?- read(X). |: fido. X = fido
read
continuedNormally one uses read
with a variable as parameter,
but:
?- read(ok). |: ok. true. ?- read(jane). |: fido. fail. ?- read(likes(jane, pizza)). |: likes(jane, pizza). true. ?- read(likes(jane, X)). |: likes(jane, pizza). X = pizza
tab
The built-in predicate tab(N)
causes N
spaces to be output:
?- write(hi), tab(1), write(there),nl. hi there true. ?- write(hi), tab(15), write(there),nl. hi there true.
?- tell(myfile), | write(hello_myfile), nl, | told, | write(writing_to_screen_again), nl. writing_to_screen_again true. ?- ^D % cat myfile hello_myfile
tell
& told
continued? tell(myfile1), write(thing1), nl, | tell(myfile2), write(thing2), nl, | tell(myfile1), write(thing3), nl, | told, write(thing4), nl. thing4 true. % cat myfile1 thing1 thing3 % cat myfile2 thing2
Since myfile1
wasn't closed, write(thing3)
appends to myfile1
.
see
& seen
% cat mydata likes(jane, pizza). eats(fido, bone). % prolog ?- see(mydata), read(X), read(Y), seen, read(Z). |: the_end. X = likes(jane, pizza) Y = eats(fido, bone) Z = the_end
see(mydata)
and tell(myfile)
work, as
above. If the file name has the form of a Prolog atom, you can just use
the atom: see(mydata)
and tell(myfile)
. If
the file name has an extension or requires a path, then single quotation
marks are necessary:
see('mydata.dat') see('/home/cs9414/public/mydata.dat') see('/home/cs9414/public/mydata') tell('myfile.out') tell('/home/cs9414/public/myfile.out') tell('/home/cs9414/public/myfile')
% cat procfil.pl processfile :- read(Term), Term \== end_of_file, process1(Term). % we only get to the rule below if the one above fails, % which happens if end of file is encountered. processfile :- !. % succeeds and blocks backtracking; % i.e. terminates processfile. process1(Term) :- write(Term), nl, processfile.
% cat moredata this(old, man). he(plays, one). he(plays, nik, nak). on(my, tum). % prolog -s procfil.pl ?- see(moredata), processfile, seen. this(old, man) he(plays, one) he(plays, nik, nak) on(my, tum) true.
put
- writing one character at a time?- put(C).
writes the character C
on the current output stream.
For example,
?- put('f'), put('i'), put('d'), put('o'), nl. fido true.
C
may in fact be either a character or an
integer-expression in the range 0 to 255 - i.e. a character
code:
?- put(102), put(105), put(100), put(111), nl. fido true.
get
& get_byte
- reading one character at a time?- get_byte(C). |: abcde C = 97
get(C)
differs in that it reads the first non-blank
character.
getc
continued, flush_output
read_a_char(C) :- write('Type: '), flush_output, get_byte(C).Then
?- read_a_char(X). Type: + X = 43
atom_chars
and other operations on atoms?- atom_chars(A, [f, i, d, o]). A = fido ?- atom_chars(fido, L). L = [f, i, d, o] ?- atom_codes(A, [102, 105, 100, 111]). A = fido ?- atom_codes(fido, L). L = [102, 105, 100, 111]
consult
% prolog -s mycode1.pl
starts SWI Prolog with the Prolog code in mycode1.pl
loaded up (assuming it contains syntactically correct code).
Sometimes it is more convenient to load a file of Prolog code from
within SWI Prolog. consult(File)
allows this.
% prolog mycode1.pl ?- consult('mycode2.pl'). true.
consult
2% prolog -s mycode1.pl ?- likes(mary, pizza). true. ?- consult('mycode2.pl'). Warning: (//mycode2.pl:1): Redefined static procedure likes/2 % mycode2 compiled 0.00 sec, 520 bytes true. ?- likes(mary, pizza). fail. ?- likes(fido, bone). true.
listing
listing
prints the current contents of the Prolog database
(removing comments and replacing variable names):
% prolog -s procfil.pl ?- listing. processfile :- read(A), A\==end_of_file, process1(A). processfile :- !. process1(A) :- write(A), nl, processfile. % Foreign: rl_read_init_file/1 % Foreign: rl_add_history/1 true.
Aims |
To study "metalogical predicates" in Prolog, including:
|
---|---|
References | Bratko, ed. 3, chapter 7; SWI Prolog documentation |
Keywords |
var , nonvar , atom , integer , number , atomic ,
=.. , functor , arg ,
assert , asserta , assertz , retract ,
fail , true , repeat , call ,
setof , findall
|
It may be necessary to know if a term is an number or a variable or something else.
Goal | succeeds if X is currently ... |
---|---|
var(X) | an uninstantiated variable |
nonvar(X) | not a variable, or is an instantiated variable |
atom(X) | bound to an atom |
integer(X) | bound to an integer |
number(X) | bound to a number (integer or float) |
atomic(X) | bound to a number, string, or atom |
?- Goal =.. [likes, mary, pizza]. Goal = likes(mary, pizza).
?- RelationName = likes, Goal =.. [RelationName, mary, pizza]. RelationName = likes, Goal = likes(mary, pizza).
You can also take terms apart with =..
:
?- likes(mary, pizza) =.. List. List = [likes, mary, pizza].
You can then test your goal, in SWI Prolog, just by stating it:
?- likes(mary, pizza). true. ?- Goal =.. [likes, mary, pizza], Goal. Goal = likes(mary, pizza) ?- Goal =.. [likes, fido, cheese], Goal. true.
In other dialects of Prolog, you might need to use
call(Goal)
to test the goal:
?- Goal =.. [likes, mary, pizza], call(Goal).
univ also works with infix operators:
?- Term =.. [+, 2, 3], Value is Term. Term = 2+3, Value = 5
functor
and arg
?- functor(likes(mary, pizza), Func, Arity). Func = likes Arity = 2 ?- arg(2, likes(mary, pizza), Arg). Arg = pizza
Comparison | Definition |
---|---|
X = Y | X and Y match in the Prolog sense |
X \= Y | X and Y do not match in the Prolog sense |
X is Exp | true if X matches the value of the arithmetic expression Exp |
T1 == T2 | true if terms T1 and T2 are identical; e.g. names of variables have to be the same |
T1 \== T2 | true if terms T1 and T2 are not identical |
E1 =:= E2 | true if values of expressions E1 and E2 are equal |
E1 =\= E2 | true if values of expressions E1 and E2 are not equal |
E1 < E2 | true if value of expression E1 is < value of E2 |
X @< Y | true if value of X alphabetically precedes value of Y |
Also
>
,
>=
,
=<
,
@>
,
@=<
,
@>=
all work as you would expect.
?- likes(X, pizza) = likes(mary, Y). X = mary Y = pizza ?- likes(X, pizza) == likes(mary, Y). fail. ?- X = 3, Y = 5, X + 4 =:= Y + 2. X = 3 Y = 5 ?- X = 3, Y = 5, X + 4 =\= Y + 3. X = 3 Y = 5
?- X = 3, Y = 5, X + 4 < Y + 3. X = 3 Y = 5 ?- mary @< pizza. true. ?- likes(mary, pizza) @< likes(rex, cheese). true.
Prolog can infer facts, using rules, other facts, and its logic engine.
assert
The built-in predicates assert
,
asserta
, and assertz
allow you to add to the database.
?- assert(likes(mary, pizza)). true. ?- listing. likes(mary, pizza). true. ?- assert((happy(X) :- is_dog(X), walkies(X))). true. ?- listing. happy(A) :- is_dog(A), walkies(A). likes(mary, pizza) . true.
retract
retract
allows you to remove facts and rules from the database.
?- retract(likes(mary, pizza)). true. ?- listing. happy(A) :- is_dog(A), walkies(A). true.
retractall
% prolog ?- assert(dog(fido)). ?- assert(dog(rex)). ?- assert(dog(fang)). ?- retractall(dog(X)). true. ?- listing. true. % no clauses in database.
asserta
and assertz
assert
gives no control over where in the database the
new fact or rule is positioned.
asserta
does this. For completeness,
assertz
puts the new fact/rule at the end
of the database (or rather at the end of the facts/rules
with the same functor and arity). In SWI Prolog assert
and
assertz
are equivalent.
dynamic
ERROR: No permission to modify static_procedure `likes/2'
% cat myfacts :- dynamic likes/2. likes(mary, pizza). likes(john, fried_rat).
If you want to make several procedures dynamic, you can have, e.g.
:- dynamic likes/2, gives/3, gives/2.
fail
is a goal that always fails
true
is a goal that always succeeds
repeat
is a goal which, suitably used, does
what the name suggests.
repeat
behaves as if defined by:
repeat. repeat :- repeat.
repeat
Here is an example of using repeat
:
dountilstop :- repeat, read(X), (X = stop, ! ; dosomethingwith(X), fail ).The
fail
is there to force backtracking.
(goal1 ; goal2)
means "goal1 or goal2" - try
goal1
first and if it fails, try goal2
.
repeat
example% cat someterms prerequisite_of(comp9322, comp9321). prerequisite_of(comp9332, comp9331). prerequisite_of(comp9415, comp9024). stop. more. % cat dountilstop.pl dountilstop :- repeat, read(X), (X = stop, ! ; print(X), nl, fail ). % prolog -s dountilstop.pl ?- see(someterms). true. ?- dountilstop. prerequisite_of(comp9322, comp9321) prerequisite_of(comp9332, comp9331) prerequisite_of(comp9415, comp9024) true.
not
In Prolog, \+
, pronounced "not" is built in, but can be
defined as:
\+(P) :- P, !, fail ; true.
You may also see not(P)
, an older form, now deprecated.
\+
is supposed to be a mnemonic for not
(\
) provable (+
).
For an example of \+
in action, see The
path
Program, below.
setof
% cat happy.pl happy(fido). happy(rex). happy(lassie). % prolog -s happy.pl ?- setof(X, happy(X), List). List = [fido, lassie, rex]
findall
What if you want to keep the duplicates?
% cat happy2.pl movie_star(rintintin). movie_star(lassie). happy(fido). happy(rex). happy(lassie). happy(X) :- movie_star(X). % prolog -s happy2.pl ?- setof(X, happy(X), List). List = [fido, lassie, rex, rintintin] ?- findall(X, happy(X), List). List = [fido, rex, lassie, rintintin, lassie]
Aims |
To study data structures and their implementation in Prolog, including:
|
---|---|
References | Bratko, ed. 3: arrays p. 188, graphs p. 215 |
Keywords |
arg ,
push ,
pop ,
empty , join , serve ,
edge ,
vertex .
|
?- [1, 2, 3, 4] = [First | Rest]. First = 1 Rest = [2, 3, 4]
% empty(+Stack) succeeds if the Stack is empty empty([]). % pop(-Item, +OldStack, -NewStack) % takes the Item from the head of the % OldStack to produce the NewStack pop(Item, [Item | NewStack], NewStack). % push(+Item, +OldStack, -NewStack) % adds Item to the *front* of the OldStack % to make NewStack push(Item, OldStack, [Item | OldStack]).
% empty(+Queue) which succeeds if the Queue is % empty - same implementation as for stacks % join(+Item, +OldQueue, -NewQueue) % adds Item to the *end* of the OldQueue % to make NewQueue join(Item, [], [Item]). join(Item, [First | Rest], [First | NewRest]) :- join(Item, Rest, NewRest). % serve(-Item, +OldQueue, -NewQueue) % takes the Item from the head of the % OldQueue to produce the NewQueue serve(Item, [Item | NewQueue], NewQueue). % NB: this does the same thing as pop
A graph may be represented by a set of edge predicates and a list of vertices.
edge(1, 5). edge(1, 7). edge(2, 1). edge(2, 7). edge(3, 1). edge(3, 6). edge(4, 3). edge(4, 5). edge(5, 8). edge(6, 4). edge(6, 5). edge(7, 5). edge(8, 6). edge(8, 7). vertex(1). vertex(2). vertex(3). vertex(4). vertex(5). vertex(6). vertex(7). vertex(8).
edge(maroubra, 3.2, kensington).
meaning that it is 3.2 kilometres from Maroubra to Kensington.
path(+Start, +Finish, +Visited, -Path).
Start
| is the name of the starting node |
Finish
| is the name of the finishing node |
Visited
| is the list of nodes already visited - this is an "accumulator" |
Path
| is the list of nodes on the path, including Start and Finish |
path(Node, Node, _, [Node]).
path(Start, Finish, Visited, [Start | Path]) :- edge(Start, X), \+(member(X, Visited)), path(X, Finish, [X | Visited], Path).
To find the paths from, say, vertex 1 to vertex 3, we'd use the Prolog query:
?- path(1, 3, [1], Path).
Here is an example of the path algorithm in action. There are lots of other things to do with graphs. Some are in Bratko.
Most programming languages provide facilities that are convenient for representing matrices and vectors. Prolog does not. It is possible to represent things like this in Prolog, but it can be cumbersome. For example, you can represent a vector as a list of numbers, and a matrix as a list of lists of numbers:
[1, 2, 3, 4, 5] [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Accessing the items is inconvenient - to get the 4th item in the vector, you have to step through all the preceding items. To access the (2,3)-entry in the matrix, you have to step through to the 2nd row, and then step through that to the 3rd item.
A few operations turn out to be easy to implement - e.g. the inner or dot product of two vectors is fine.
It is possible to simulate arrays in Prolog, to a degree, using
terms, and the arg
built-in procedure. For example, you
can build a vector term like vector(1, 3, 5, 7, 9)
and then to access say the 4th item in it, you can use the goal
?- arg(4, vector(1, 3, 5, 7, 9), Value). Value = 7
Unlike in languages like C, it is not possible to update the values in such terms, except by making a new copy of the whole structure with the desired value changed. This would be slow if done repeatedly, as is often the case with array structures in C.
An alternative is to write a file with the numerical computation problem specified, get a C program to do the computation, then it writes a file with the answer in it, and a/the Prolog program then reads the answer and proceeds.
Aims |
Prolog Programming Principles and Techniques :
|
---|---|
References | Bratko, ed. 3: chapter 8 |
Keywords | correctness, usability, efficiency, readability, modifiability, robustness, documentation |
These are covered in other CSE courses, but not in COMP9414.
Obvious. But in fact, all large-enough programs have some bugs.:-(
A slow, hard-to-use, non-robust program that produces correct results is preferable to one that produces radically wrong results.
There are psychological principles that provide a guide as to what sort of user interface is easy to use. You can find out about this in COMP9511 Human-Computer Interaction.
Programs shouldn't waste time or memory space.
This includes RAM memory but also disk file space.
An inefficient but correct program running on a large
problem may not find a result (in time to be useful).
Spending time on speeding up a fast-enough program is
however pointless - the dollar-advantage of making the program
more efficient needs to be weighed against the dollar-cost.
Issues like this are covered in COMP9101 Design & Analysis
of Algorithms.
Your program should be as easy to understand as possible. If it isn't, you probably don't understand it, which means it may be wrong. If you use some weird trick you read about or thought up - explain what it does.
If your program is worthwhile, it may be used for years, and modified (or corrected) by other programmers. You need to make their job as easy as possible.
Using helpful comments and meaningful identifiers will help to make your program readable. So will layout: indent systematically and use blank lines to break up your code into natural units.
Apart from making your program readable (see above), use of modular structure and clear interfaces to your procedures makes your program easier to test and modify.
In other words:
I specifically de-emphasise this in COMP9414, but a good program needs to be able recover from input data that is in an unexpected format or which doesn't meet the assumptions you make about the data.
In the case of data format, you can "parse" the data - e.g. check that you really have read a number (if a number is what you are expecting).
An example of an unmet assumption would be when you go to divide one number by another, only to find that the second number is zero. Solution: check the divisor before you divide. In general, check the assumption holds before you apply the operation that relies on that assumption.
Documentation comes in two flavours - internal (comments, etc. in the code), and external (user reference manual, user tutorial manual).
In describing a predicate in its header comment, it can be
helpful to specify which arguments are meant to be input (+
)
and which are meant to be outputs (-
). So, if you write a procedure
sort(List, SortedList)
to sort a List
,
producing a SortedList
, you can briefly indicate
the uses of the parameters by writing
sort(+List, -SortedList)
.
Some parameters can be used as either input or output - but
sort
is not like this. Such parameters can be indicated
using ?
rather than +
or -
.
For example, the built-in atom_chars
could have its
uses indicated by the expression atom_chars(?Atom, ?List)
along with the remark that at least one of Atom
and
List
must be instantiated (see above).
There are two ways to execute a Prolog program.
The first - interpretation - involves taking the rules and facts in the text format in which they are supplied to Prolog and "interpreting" them - that is, figuring out what each goal means, and executing it, then figuring out what the next goal means, and executing that, and so on.
The second way - compilation - involves translating each rule into machine code. When the compiled code is executed, it is typically 10 times faster than interpreted code.
To compile code with SWI Prolog, do something like
% prolog -c mycode.plThe compiled code will appear in a file called
a.out
% a.outthen executes this code. [I have done minimal experimentation with this SWI Prolog facility.]
Compilation also provides an opportunity to automatically "improve" the code, which might make it faster still. One area, particularly relevant to Prolog, where this might help, is the elimination of tail-recursion.
Tail-recursion is where the recursive goal is the very last goal of the Prolog rule. When this happens, the recursion can be converted into a loop, which can be executed more efficiently, because of the overheads associated with a procedure call (in this case a sequence of recursive procedure calls).
sumlist
Tail-Recursive% sumlist(+List, -Sum) - Sum is the sum of numbers in List sumlist([], 0). sumlist([First | Rest], Sum) :- sumlist(Rest, SumRest), Sum is First + SumRest. % not tail-recursive
sumlist
Tail-Recursive 2% Ivan Bratko, ed. 3, p 187 sumlist(List, Sum) :- sumlist(List, 0, Sum). % call auxiliary predicate % sumlist/3 % sumlist(+List, PartialSum, -TotalSum): % TotalSum = PartialSum + sum over List sumlist([], Sum, Sum). sumlist([First | Rest], PartialSum, TotalSum) :- NewPartialSum is PartialSum + First, sumlist(Rest, NewPartialSum, TotalSum).
reverse
Tail-Recursive% reverse(+List, -ReversedList) reverse([], []). reverse([X | Rest], Reversed) :- reverse(Rest, RevRest), concat(RevRest, [X], Reversed). % append [X] at end
This is also inefficient because concat takes time proportional
to the length of RevRest
, which increases as more and more of
the list gets reversed.
reverse
Tail-Recursive 2% Ivan Bratko, ed. 3, p 188 reverse(List, Reversed) :- reverse(List, [], Reversed). % reverse/3 % reverse(+List, PartReversed, -Reversed) % Reversed is obtained by adding the elements of List in % reversed order to PartReversed reverse([], Reversed, Reversed). reverse([X | Rest], PartReversed, TotalReversed) :- reverse(Rest, [X | PartReversed], TotalReversed). % add X to accumulator
reverse
Tail-Recursive 3 % prolog -s reverse.pl ?- trace. true. [trace] ?- reverse([1,2,3], X). Call: (7) reverse([1, 2, 3], _G293) invoke the rule for reverse/2 Call: (8) reverse([1, 2, 3], [], _G293) invoke rule 2 for reverse/3 Call: (9) reverse([2, 3], [1], _G293) 2nd invocation rule 2 for reverse/3 Call: (10) reverse([3], [2, 1], _G293) 3rd invocation rule 2 for reverse/3 Call: (11) reverse([], [3, 2, 1], _G293) invoke rule 1 for reverse/3 Exit: (11) reverse([], [3, 2, 1], [3, 2, 1]) rule 1 succeeds at once - no RHS Exit: (10) reverse([3], [2, 1], [3, 2, 1]) | successive completions of Exit: (9) reverse([2, 3], [1], [3, 2, 1]) | of the calls to rule 2, Exit: (8) reverse([1, 2, 3], [], [3, 2, 1]) | reverse/3 Exit: (7) reverse([1, 2, 3], [3, 2, 1]) rule for reverse/2 succeeds X = [3, 2, 1]
UNSW's CRICOS Provider No. is 00098G
Last updated on: 15 March 2010
Copyright (C) Bill Wilson, 1996-2008, except where another source is acknowledged.
The presentation of the path
algorithm derives from lectures
by Claude Sammut.