You are on page 1of 45

INTRODUCTION

Logic programming is a programming paradigm based on mathematical logic. In this


paradigm the programmer specifies relationships among data values (this constitutes a logic
program) and then poses queries to the execution environment (usually an interactive
interpreter) in order to see whether certain relationships hold. Putting this in another way, a
logic program, through explicit facts and rules, defines a base of knowledge from which
implicit knowledge can be extracted. This style of programming is popular for data base
interfaces, expert systems, and mathematical theorem provers. In this course you will be
introduced to Prolog, the primary logic programming language, through the interactive SWI-
Prolog system (interpreter) and also LISP.

Logic programming differs fundamentally from conventional programming in requiring us


to describe the logical structure of problems rather than making us prescribe how the
computer is to go about solving them. People with no previous computing experience tend
to believe that programming is, and always has been, a logical business, and when
introduced to languages like Pascal are often surprised to find that this is not really the case.
Instead they discover that the traditional way of writing programs pays much homage to the
computer’s internal mechanisms, which, whilst certainly having a rationale of their own, do
not seem to derive straight forwardly from the original conception of the problem.
Conversely, programmers trained only in the use of conventional languages can experience
comparable problems of adjustment when introduced to logic programming. Instinctively
anxious to control the machine efficiently, they are subject to a vague sense of deprivation
when getting used to a language possessing no machine oriented features.

Consider the following natural language statement: “you are healthy if you eat porridge.”
This sentence can be structured into a sentence in symbolic logic when arrange this way:
healthy (u) if eat (u, porridge)

This format exposes all the principal constituents of the language as we shall use it for
programming purpose:
- Individual objects: porridge

Page 1 of 45 BIT 312 Notes – Logic Programming


- Variables: u (standing for any object)
- Propositions: healthy (u) and eat (u, porridge) and
- Connectives: if (relating the propositions)

This sentence might form part of an expert-system program offering advice about personal
nutrition.

We can thus, describe even numbers logically as: even-number(x) if divisible(x, 2). But how
can logical description of this kind be used to make a computer solve a problem?

Analogy: You wish to undertake a car journey having decided the origin, destination and possibly
other defining features of the route. Getting the car traverse this route entails multifarious decisions,
many of them petty and repetitious, about vehicle-handling and motoring protocols. It is now proposed
that the journey be accomplished without your having to make any of these decisions. How is this
possible? By engaging someone else to do the driving and telling him the requirements of the route.

The logic programmer’s driver’ is the logic interpreter. This is a program which knows how
to exploit the computer in order to infer the consequences of any set of logical sentences. The
programmer’s responsibility is to ensure that the given sentences are correct and sufficiently
informative to make the desired consequences inferable.

Example: Imagine that some piece of equipment exhibits a three-light display monitor each light is
either ON or OFF. Various ON/OFF combination on this display periodically determine whether
some switch on the equipment is to be set to ON or OFF by a human operator.

Analysis/Solution: Using logic we can write a collection of simple assertions/rules.


R1: rule(ON, ON, OFF, ON)
R2: rule(OFF, ON, ON, OFF)
.
.
Etc.

Page 2 of 45 BIT 312 Notes – Logic Programming


Where each proposition rule(w, x, y, z,) is read as saying that the switch is to be set to the
state w when the display’s state is x, y, z jointly these sentences can be used as a decision
table. Suppose we wish to store this table in a computer so that the operator can interrogate it
in order to find out which state w is the appropriate response to some x, y, z displayed on the
monitor. Given a logic interpreter implemented on the computer, the operator need only ask
whether a particular, proposition is a consequence of (implied by) the stored sentences. For
example he can type in the logic query.
? rule(w, ON, OFF, ON)

Which asks which value of w makes rule(w, ON, OFF, ON) such a consequence. According to
R1 this value is ON and so the interpreter will autonomously discover this and print out w:=
ON.

Many other queries are answerable in the same manner (i.e. without altering the stored
sentences) by simply posing them to the interpreter. The query ? rule(OFF, x, y, z) returns all
states x, y, z of the display requiring the OFF response. The query ? rule(OFF, ON, ON,
OFF) merely asks for confirmation that OFF is a correct response to the display ON, ON,
OFF; the interpreter just answers “YES” (because of R2). The query ? rule(w, x, y, z) elicits a
printout of the entire decision table. The query ? rule(w1, x, y, z), ? rule(w2, x, y, z), w1≠w2
instigates a table-consistency check by asking whether any display x, y, z have multiple
occurrences in the table with contradictory responses. If we store in the machine two further
sentences.
S1: State(ON)
S2: State(OFF)

Then the query ? State(x), ? State(y), ? state(z), ~rule(w, x, y, z) instigates a table


completeness check by asking whether any display x, y, z have been omitted from the table
(~ means ‘not’) and, if so, tells us what they are.

In short, every possible query that is logically answerable using the stored sentences will be
answered by the interpreter using logical inference. In virtually every other programming

Page 3 of 45 BIT 312 Notes – Logic Programming


language each new problem to be solved using a fixed corpus of knowledge requires the
laborious construction of new code, and the more complicated the derivation of the
problem’s solution, the more complicated that code needs to be. This inflexibility of
programs in the face of changing goals must detract significantly from programming
productivity.

REPRESENTATION AND REASONING


A logic program consists of sentences expressing knowledge relevant to the problem that the
program is intended to solve. The formulation of this knowledge makes use of two basic
concepts: the existence of discrete objects, referred to here as individuals; and the existence of
relations between them. The individuals considered in the context of a particular problem
jointly constitute the domain of that problem. For example, if the problem is to solve an
algebraic equation, then the domain may consist of - or at least include - the real numbers.

In order to represent individuals and relations in a symbolic system such as logic, they must
be given names. Naming is a preliminary task in creating symbolic models representing
what we know. However, the main task is to construct sentences expressing various logical
properties of the named relations. Reasoning about some problems posed on the domain can
be achieved by manipulating these sentences using logical inference. In a typical logic
programming environment the programmer invents the sentences forming his program and
the computer then performs the necessary inference to solve the problem.

Individuals
Individuals may be any objects at all. Examples are numbers, geometrical figures, equations
and computer programs. Simple names like CIRCLE, CAKE, TWO or EQN can be given to
objects. These names are indivisible (on unstructured) and are conventionally called
constants.
Sometimes it is convenient to give individuals composite structured names like TWICE(2)
and PLUS(1, 2). Each of these consists of an n-tuple prefixed by a functor (or function
symbol). An n-tuple is any ordered collection of n names, so (1, 2 is an example of 2-tuple.
Functors like TWICE and PLUS are chosen arbitrary from another prescribed vocabulary.

Page 4 of 45 BIT 312 Notes – Logic Programming


Each one can only prefix n-tuples for a particular value of n, and is then said to be an n-place
(or n-ary) functor. So in our examples above, TWICE is a 1-palce (1-ary or ‘unary’) functor
whilst PLUS is a 2-place (or 2- nary or ‘binary’) functor.

With functors, elaborate names can be constructed such as:


PLUS(TWICE (2), PLUS(1. TWICE (1)))
Which stands for the seventh positive integer.

Relations
A symbol like TWICE has no intrinsic meaning and so does not, in its own right, correspond
to our intuitive idea of ‘twice’. One way of capturing the idea is to choose names 1,2,3 … etc
for the numbers and then collect then into pairs to form the set shown below, which has been
given the name twice.
Twice = {(1, 2), (2, 4), (3, 6), … etc}
The occurrence in this set of any pair (x, y) can then signify ‘twice x equals y’ and the entire
set can considered to encompass the entire concept of ‘twice’

This set is an example of a relation. An n-place relation is just any set of n-place for some
particular value of n. so twice here names a 2-place or binary relations; if relates the
individuals named within each n-tuple belonging to it.

Every idea is representable in many different ways. For example, we could define another,
more general relation named equals containing such pairs as
(TWICE (1), 2), (TWICE (2), 4) (ONCE (1), 1) (THRICE (2), 6)

Then the occurrence of any pair (x, y) in equals can signify ‘x equals y’. In particular a pair
(TWICE (x), y) in equals can signify ‘twice x equals y’. Then the idea ‘twice’ is represented by
the set of all such pairs belonging to equals.

The degree to which a relation represents an idea depends not upon the evocative nature of
symbols like twice but rather upon the logical features of the relation itself.

Page 5 of 45 BIT 312 Notes – Logic Programming


LOGIC PROGRAMMING IN PROLOG
Brief Background
The name PROLOG stands for PROgramming in LOGic. The language was devised in 1972
at the University of Marseilles, with the main purpose of assisting with the task of proving
mathematical theorems, hence its firm base in formal logic. Since then and until quite
recently, the language has only occasionally been implemented, and then only on mainframe
or minicomputers mainly in the USA. It has come out of the closet in the mid-80s when the
Japanese recognized its database and query potential as the basis for their knowledge-based
systems projected for the 1990s (fifth generation).

Facts, Rules, and Queries


This introductory lecture has two main goals:

1. To give some simple examples of Prolog programs. This will introduce us to the three
basic constructs in Prolog: facts, rules, and queries. It will also introduce us to a number of
other themes, like the role of logic in Prolog, and the idea of performing matching with the
aid of variables.
2. To begin the systematic study of Prolog by defining terms, atoms, variables and other
syntactic concepts.

There are only three basic constructs in Prolog: facts, rules, and queries. A collection of facts
and rules is called a knowledge base (or a database) and Prolog programming is all about
writing knowledge bases. That is, Prolog programs simply are knowledge bases, collections
of facts and rules which describe some collection of relationships that we find interesting. So
how do we use a Prolog program? By posing queries. That is, by asking questions about the
information stored in the knowledge base. Now this probably sounds rather strange. It’s
certainly not obvious that it has much to do with programming at all – after all, isn’t
programming all about telling the computer what to do? But as we shall see, the Prolog way
of programming makes a lot of sense, at least for certain kinds of applications (computational
linguistics being one of the most important examples). But instead of saying more about

Page 6 of 45 BIT 312 Notes – Logic Programming


Prolog in general terms, let’s jump right in and start writing some simple knowledge bases;
this is not just the best way of learning Prolog, it’s the only way ...

SETTING UP A DATABASE (KNOWLEDGE BASE)


1.1.1 Knowledge Base 1 (Fact)
The main activity of PROLOG programming is to establish a database of facts. This database
mainly consists of facts and figures about a particular problem. Knowledge Base 1 (KB1) is
simply a collection of facts. Facts are used to state things that are unconditionally true of the
domain of interest. For example, we can state that Elizabeth, Anne, and Diana are females,
and that Anne plays air guitar, using the following four facts:

female(elizabeth).
female(anne).
female(diana).
playsAirGuitar(anne).

This collection of facts is KB1. It is our first example of a Prolog program. Note that the
names elizabeth, anne, and diana, and the properties female and playsAirGuitar, have
been written so that the first letter is in lower-case. This is important; we will see why a little
later.
How can we use KB1? By posing queries. That is, by asking questions about the information
KB1 contains. Here are some examples. We can ask Prolog whether Elizabeth is a female by
posing the query:

?- female(elizabeth).
Prolog will answer
true
for the obvious reason that this is one of the facts explicitly recorded in KB1. Incidentally, we
don’t type in the ?-. This symbol (or something like it, depending on the implementation of
Prolog you are using) is the prompt symbol that the Prolog interpreter displays when it is
waiting to evaluate a query. We just type in the actual query (for example

Page 7 of 45 BIT 312 Notes – Logic Programming


female(elizabeth)) followed by . (a full stop). Similarly, we can ask whether Anne plays
air guitar by posing the following query:
?- playsAirGuitar(anne).

Prolog will again answer “true”, because this is one of the facts in KB1. However, suppose
we ask whether Elizabeth plays air guitar:
?- playsAirGuitar(elizabeth).
We will get the answer
false
Why? Well, first of all, this is not a fact in KB1. Moreover, KB1 is extremely simple, and
contains no other information (such as the rules we will learn about shortly) which might
help Prolog try to infer (that is, deduce whether Elizabeth plays air guitar. So Prolog
correctly concludes that playsAirGuitar(elizabeth) does not follow from KB1.
Here are two important examples. Suppose we pose the query:
?- playsAirGuitar(george).

Again Prolog answers “false”. Why? Well, this query is about a person (George) that it has
no information about, so it concludes that playsAirGuitar(george) cannot be deduced
from the information in KB1.
Similarly, suppose we pose the query:
?- tatooed(anne).
Again Prolog will answer “false”. Why? Well, this query is about a property (being
tatooed) that it has no information about, so once again it concludes that the query cannot be
deduced from the information in KB1.

If we continue to enter data into our database, called KB1 file, we would add more facts like

mother-of(elizabeth, charles).
mother-of(elizabeth, anne).
mother-of(diana, william).
mother-of(diana, henry).
father-of(philip, charles).
father-of(philip, anne).
father-of(charles, william).
Page 8 of 45 BIT 312 Notes – Logic Programming
father-of(charles, henry).

Binary and Unary Relationships


The relationships we have considered above relate two objects or words together, i.e. father-
of and mother-of. These are called binary relationships because they relate two things or
objects. However, a more simple relationship is unary relationship. In normal terms unary
relationships are regarded as a single feature or characteristics of an object. An example of
this relationship is in our previous database KB1 file, Elizabeth, Anne and Diana are
females.

1.1.2 Knowledge Base 2 (Rule)

Here is KB2, our second knowledge base:

listensToMusic(elizabeth).
happy(diana).
playsAirGuitar(elizabeth) :- listensToMusic(elizabeth).
playsAirGuitar(diana) :- listensToMusic(diana).
listensToMusic(diana):- happy(diana).

KB2 contains two facts, listensToMusic(elizabeth) and happy(diana). The last three
items are rules.
Rules state information that is conditionally true of the domain of interest. For example, the
first rule says that Elizabeth plays air guitar if she listens to music, and the last rule says that
Diana listens to music if she is happy. More generally, the (:-) should be read as “if”, or “is
implied by”. The part on the left hand side of the (:-) is called the head of the rule, the part on
the right hand side is called the body. So in general rules say: if the body of the rule is true,
then the head of the rule is true too. And now for the key point: if a knowledge base contains a
rule head :- body, and Prolog knows that body follows from the information in the knowledge base,
then Prolog can infer head.

This fundamental deduction step is what logicians call modus ponens.


Let’s consider an example. We will ask Prolog whether Elizabeth plays air guitar:

?- playsAirGuitar(elizabeth).

Page 9 of 45 BIT 312 Notes – Logic Programming


Prolog will respond “true”. Why? Well, although playsAirGuitar(elizabeth) is not a
fact explicitly recorded in KB2, KB2 does contain the rule playsAirGuitar(elizabeth) :-
listensToMusic(elizabeth).

Moreover, KB2 also contains the fact listensToMusic(elizabeth). Hence Prolog can use
modus ponens to deduce that playsAirGuitar(elizabeth).

Our next example shows that Prolog can chain together uses of modus ponens. Suppose we
ask:
?- playsAirGuitar(diana).

Prolog would respond “true”. Why? Well, using the fact happy(diana) and the rule
listensToMusic(diana):- happy(diana),
Prolog can deduce the new fact listensToMusic(diana). This new fact is not explicitly
recorded in the knowledge base — it is only implicitly present (it is inferred knowledge).
Nonetheless, Prolog can then use it just like an explicitly recorded fact. Thus, together with
the rule
playsAirGuitar(diana) :- listensToMusic(diana)

it can deduce that playsAirGuitar(diana), which is what we asked it. Summing up: any
fact produced by an application of modus ponens can be used as input to further rules. By
chaining together applications of modus ponens in this way, Prolog is able to retrieve
information that logically follows from the rules and facts recorded in the knowledge base.
The facts and rules contained in a knowledge base are called clauses. Thus KB2 contains five
clauses, namely three rules and two facts. Another way of looking at KB2 is to say that it
consists of three predicates (or procedures). The three predicates are:
listensToMusic
happy
playsAirGuitar

The happy predicate is defined using a single clause (a fact). The listensToMusic and
playsAirGuitar predicates are each defined using two clauses (in both cases, two rules). It
is a good idea to think about Prolog programs in terms of the predicates they contain. In
essence, the predicates are the concepts we find important, and the various clauses we write

Page 10 of 45 BIT 312 Notes – Logic


Programming
down concerning them are our attempts to pin down what they mean and how they are
inter-related.
One final remark. We can view a fact as a rule with an empty body. That is, we can think of
facts as “conditionals that do not have any antecedent conditions”, or “degenerate rules”.

1.1.3 Knowledge Base 3


KB3, our third knowledge base, consists of five clauses:
happy(george).
listensToMusic(butch).
playsAirGuitar(george):- listensToMusic(george), happy(george).
playsAirGuitar(butch):- happy(butch).
playsAirGuitar(butch):- listensToMusic(butch).
There are two facts, namely happy(george) and listensToMusic(butch), and
three rules. KB3 defines the same three predicates as KB2 (namely happy,
listensToMusic, and playsAirGuitar) but it defines them differently. In particular, the
three rules that define the playsAirGuitar predicate introduce some new ideas. First, note
that the rule:
playsAirGuitar(george):- listensToMusic(george),
happy(george).

has two items in its body, or (to use the standard terminology) two goals. What does this rule
mean? The important thing to note is the comma , that separates the goal
listensToMusic(george) and the goal happy(george) in the rule’s body. This is the way
logical conjunction is expressed in Prolog (that is, the comma means and). So this rule says:
“George plays air guitar if he listens to music and he is happy”.
Thus, if we posed the query:

?- playsAirGuitar(george).

Prolog would answer “false”. This is because while KB3 contains happy(george), it does
not explicitly contain the information listensToMusic(george), and this fact cannot be
deduced either. So KB3 only fulfils one of the two preconditions needed to establish
playsAirGuitar(george), and our query fails. Incidentally, the spacing used in this rule is
irrelevant. For example, we could have written it as playsAirGuitar(George):-

Page 11 of 45 BIT 312 Notes – Logic


Programming
happy(george),listensToMusic(george). and it would have meant exactly the same
thing. Prolog offers us a lot of freedom in the way we set out knowledge bases, and we can
take advantage of this to keep our code readable.

Next, note that KB3 contains two rules with exactly the same head, namely:
playsAirGuitar(butch):- happy(butch).
playsAirGuitar(butch):- listensToMusic(butch).

This is a way of stating that Butch plays air guitar if either he listens to music, or if he is
happy. That is, listing multiple rules with the same head is a way of expressing logical
disjunction (that is, it is a way of saying or). So if we posed the query
?- playsAirGuitar(butch).
Prolog would answer “true”. For although the first of these rules will not help (KB3 does not
allow Prolog to conclude that happy(butch)), KB3 does contain listensToMusic(butch)
and this means Prolog can apply modus ponens using the rule
playsAirGuitar(butch):-listensToMusic(butch). to conclude that
playsAirGuitar(butch).
There is another way of expressing disjunction in Prolog. We could replace the pair of rules
given above by the single rule
playsAirGuitar(butch):- happy(butch); listensToMusic(butch).
That is, the semicolon ; is the Prolog symbol for or, so this single rule means exactly the same
thing as the previous pair of rules. But Prolog programmers usually write multiple rules, as
extensive use of semicolon can make Prolog code hard to read. It should now be clear that
Prolog has something do with logic: after all, the (:-) means implication, the (,) means
conjunction, and the (;) means disjunction.
Moreover, we have seen that a standard logical proof rule (modus ponens) plays an
important role in Prolog programming. And in fact “Prolog” is short for “Programming in
logic”.

1.1.4 Here is KB4, our fourth knowledge base:

female(elizabeth).
female(anne).
Page 12 of 45 BIT 312 Notes – Logic
Programming
female(diana).
loves(george,elizabeth).
loves(samuel,elizabeth).
loves(pumpkin,honey_bunny).
loves(honey_bunny,pumpkin).

Now, this is a pretty boring knowledge base. There are no rules, only a collection of facts.
Ok, we are seeing a relation that has two names as arguments for the first time (namely the
loves relation), but, let’s face it, that’s a rather predictable idea. No, the novelty this time lies
not in the knowledge base, it lies in the queries we are going to pose. In particular, for the first
time we’re going to make use of variables.
Here’s an example:

?- female(X).

The X is a variable (in fact, any word beginning with an upper-case letter is a Prolog variable,
which is why we had to be careful to use lower-case initial letters in our earlier examples).
Now a variable isn’t a name, rather it’s a “placeholder” for information.
That is, this query essentially asks Prolog: tell me which of the individuals you know about a
female is? Prolog answers this query by working its way through KB4, from top to bottom,
trying to match (or unify) the expression female(X) with the information KB4 contains.

The first item in the knowledge base is female(elizabeth). So, Prolog matches X to
elizabeth, thus making the query agree perfectly with this first item. (Incidentally, there’s a
lot of different terminology for this process: we can also say that Prolog instantiates X to
elizabeth, or that it binds X to elizabeth.) Prolog then reports back to us as follows: X =
elizabeth
That is, it not only says that there is information about at least one woman in KB4, it actually
tells us who she is. It didn’t just say “true”, it actually gave us the variable binding, or
instantiation that led to success.
But that’s not the end of the story. The whole point of variables — and not just in Prolog
either — is that they can “stand for” or “match with” different things. And there is

Page 13 of 45 BIT 312 Notes – Logic


Programming
information about other women in the knowledge base. We can access this information by
typing the following simple query
?- ;

Remember that (;) means or, so this query means: are there any more females? So Prolog
begins working through the knowledge base again (it remembers where it got up to last time
and starts from there) and sees that if it matches X with anne, then the query agrees perfectly
with the second entry in the knowledge base. So it responds: X = anne
It’s telling us that there is information about a second female in KB4, and (once again) it
actually gives us the value that led to success. And of course, if we press (;) a second time,
Prolog returns the answer:
X = diana
But what happens if we press (;) a third time? Prolog responds “false”. No other matches
are possible. There are no other facts starting with the symbol female. The last four entries in
the knowledge base concern the love relation, and there is no way that such entries can
match a query of the form female(x). Let’s try a more complicated query, namely:
loves(samuel,X),female(X).

Now, remember that (,) means and, so this query says: is there any individual X such that
Samuel loves X and X is a female? If you look at the knowledge base you’ll see that there is:
Elizabeth is a felame (fact 1) and Samuel loves Elizabeth (fact 5). And in fact, Prolog is
capable of working this out. That is, it can search through the knowledge base and work out
that if it matches X with Elizabeth, then both conjuncts of the query are satisfied. So Prolog
returns the answer
X = elizabeth

This business of matching variables to information in the knowledge base is the heart of
Prolog. For sure, Prolog has many other interesting aspects — but when you get right down
to it, it’s Prolog’s ability to perform matching and return the values of the variable binding to
us that is crucial.

1.1.5 Knowledge Base 5

Page 14 of 45 BIT 312 Notes – Logic


Programming
Well, we’ve introduced variables, but so far we’ve only used them in queries. In fact,
variables not only can be used in knowledge bases, it’s only when we start to do so that we
can write truly interesting programs. Here’s a simple example, the knowledge base KB5:
loves(george,elizabeth).
loves(samuel,elizabeth).
loves(pumpkin,honey_bunny).
loves(honey_bunny,pumpkin).
jealous(X,Y) :- loves(X,Z),loves(Y,Z).

KB5 contains four facts about the loves relation and one rule. (Incidentally, the blank line
between the facts and the rule has no meaning: it’s simply there to increase the readability.
As we said earlier, Prolog gives us a great deal of freedom in the way we format knowledge
bases.) But this rule is by far the most interesting one we have seen so far: it contains three
variables (note that X, Y, and Z are all upper-case letters). What does it say?
In effect, it is defining a concept of jealousy. It says that an individual X will be jealous of an
individual Y if there is some individual Z that X loves, and Y loves that same individual Z
too. (Ok, so jealously isn’t as straightforward as this in the real world ...) The key thing to
note is that this is a general statement: it is not stated in terms of elizabeth, or pumpkin, or
anyone in particular — it’s a conditional statement about everybody in our little world.
Suppose we pose the query:
?- jealous(samuel,W).

This query asks: can you find an individual W such that Samuel is jealous of W?
George is such an individual. If you check the definition of jealousy, you’ll see that Samuel
must be jealous of George, because they both love the same woman, namely Elizabeth. So
Prolog will return the value
W = george

Now some questions for you, First, are there any other jealous people in KB5? Furthermore,
suppose we wanted Prolog to tell us about all the jealous people: what query would we
pose? Do any of the answers surprise you? Do any seem silly?

1.2 Prolog Syntax

Page 15 of 45 BIT 312 Notes – Logic


Programming
Now that we’ve got some idea of what Prolog does, it’s time to go back to the beginning and
work through the details more carefully. Let’s start by asking a very basic question: we’ve
seen all kinds of expressions (for example anne, playsAirGuitar(elizabeth), and X) in our
Prolog programs, but these have just been examples. Exactly what are facts, rules, and
queries built out of?

The answer is terms, and there are four kinds of terms in Prolog: atoms, numbers, variables,
and complex terms (or structures). Atoms and numbers are lumped together under the
heading constants, and constants and variables together make up the simple terms of Prolog.

Let’s take a closer look. To make things crystal clear, let’s first get clear about the basic
characters (or symbols) at our disposal. The upper-case letters are A, B, ..., Z; the lower-case
letters are a, b, ..., z; the digits are 1, 2, ..., 9; and the special characters are +, -, *, /, <, >, =, :, ., &,
~, and _. The _ character is called underscore. The blank space is also a character, but a rather
unusual one, being invisible. A string is an unbroken sequence of characters.

1.2.1 Atoms
An atom is either:
1. A string of characters made up of upper-case letters, lower-case letters, digits, and the
underscore character, that begins with a lower-case letter. For example: butch,
big_kahuna_burger, and m_monroe2.
2. An arbitrary sequence of character enclosed in single quotes. For example ’Vincent’, ’The
Gimp’, ’Five_Dollar_Shake’, ’&^%&#@$ &*’, and ’ ’. The character between the single quotes
is called the atom name. Note that we are allowed to use spaces in such atoms — in fact, a
common reason for using single quotes is so we can do precisely that.
3. A string of special characters. For example: @= and ====> and ; and :- are all atoms. As we
have seen, some of these atoms, such as ; and :- have a pre-defined meaning.

1.2.2 Numbers

Page 16 of 45 BIT 312 Notes – Logic


Programming
Real numbers aren’t particularly important in typical Prolog applications. So although most
Prolog implementations do support floating point numbers or floats (that is, representations
of real numbers such as 1657.3087 or π).
But integers (that is: ... -2, -1, 0, 1, 2, 3, ...) are useful for such tasks as counting the elements of
a list, and we’ll discuss how to manipulate them in a later lecture. Their Prolog syntax is the
obvious one: 23, 1001, 0, -365, and so on.

1.2.3 Variables
A variable is a string of upper-case letters, lower-case letters, digits and underscore
characters that starts either with an upper-case letter or with underscore. For example, X, Y,
Variable, _tag, X_526, and List, List24, _head, Tail, _input and Output are all Prolog
variables. The variable _ (that is, a single underscore character) is rather special. It’s called
the anonymous variable, and we discuss it in a later lecture.

1.2.4 Complex terms


Constants, numbers, and variables are the building blocks: now we need to know how to fit
them together to make complex terms. Recall that complex terms are often called structures.
Complex terms are built out of a functor followed by a sequence of arguments. The
arguments are put in ordinary brackets, separated by commas, and placed after the functor.
The functor must be an atom. That is, variables cannot be used as functors. On the other hand,
arguments can be any kind of term.

Now, we’ve already seen lots of examples of complex terms when we looked at KB1 – KB5.
For example, playsAirGuitar(anne) is a complex term: its functor is playsAirGuitar and its
argument is anne. Other examples are loves(george,elizabeth) and, to give an example
containing a variable, jealous(samuel,W). But note that the definition allows far more
complex terms than this. In fact, it allows us to keep nesting complex terms inside complex
terms indefinitely (that is, it is a recursive definition). For example
hide(X,father(father(father(butch)))) is a perfectly ok complex term. Its functor is
hide, and it has two arguments: the variable X, and the complex term
father(father(father(butch))). This complex term has father as its functor, and

Page 17 of 45 BIT 312 Notes – Logic


Programming
another complex term, namely father(father(butch)), as its sole argument. And the
argument of this complex term, namely father(butch), is also complex. But then the
nesting “bottoms out”, for the argument here is the constant butch.

As we shall see, such nested (or recursively structured) terms enable us to represent many
problems naturally. In fact the interplay between recursive term structure and variable
matching is the source of much of Prolog’s power.

The number of arguments that a complex term has is called its arity. For instance,
female(elizabeth) is a complex term with arity 1, while loves(george,elizabeth) is a
complex term with arity 2. Arity is important to Prolog. Prolog would be quite happy for us
to define two predicates with the same functor but with a different number of arguments.
For example, we are free to define a knowledge base that defines a two place predicate love
(this might contain such facts as love(george,elizabeth)), and also a three place love
predicate (which might contain such facts as love(george,samuel,elizabeth)).
However, if we did this, Prolog would treat the two place love and the three place love as
completely different predicates.
When we need to talk about predicates and how we intend to use them (for example, in
documentation) it is usual to use a suffix / followed by a number to indicate the predicate’s
arity. To return to KB2, instead of saying that it defines predicates
listensToMusic
happy
playsAirGuitar

we should really say that it defines predicates

listensToMusic/1
happy/1playsAirGuitar/1

And Prolog can’t get confused about a knowledge base containing the two different love
predicates, for it regards the love/2 predicate and the love/3 predicate as completely
distinct.

Logic Programming Practical


Page 18 of 45 BIT 312 Notes – Logic
Programming
In this practical you will learn:
How to start up and terminate Prolog
How to pose queries to Prolog
How to load up a program into Prolog
The edit-load-run loop
How to do simple file i/o in Prolog

We recommend you work through this practical following the items below in the order they
appear.

Starting up and terminating Prolog

Windows: You can use SWI-Prolog from the Windows. To do so, you should select start->All
Programs->SWI-Prolog->Prolog. This creates a window with SWI Prolog running in it.

You can now type at the


"?-" prompt your queries. However, you should notice:
The interpreter is "empty", that is at the moment Prolog has no facts or rules to use when
trying to answer your queries.
Don't forget to type the period at the end of your queries! If you forget it, then the interpreter
will just sit there waiting for you to type it...

You can leave Prolog at any moment by typing a control-D to the prompt. Alternatively,
select "Exit" from the "File" menu. Try exiting and starting up Prolog a couple of times.

How to pose queries to Prolog


Once you start up Prolog as shown above, you can pose queries to it. Remember that when
first started, Prolog has no rules or facts to use, so you could only use built-in predicates.
Have a look at the list of built-in predicates that SWI Prolog has on offer here but don't get
frightened if you cannot follow them at this stage.
A very important built-in is the "=" (matching/ unification/ equality) built-in. As we showed
in our lectures, it can be used to compare two terms, but it can also assign values to variables
Page 19 of 45 BIT 312 Notes – Logic
Programming
(contribute new information for the substitution that is being created as the program runs).
Try the following queries and see what Prolog does:
?- a = A.
?- a = B, B = c.
?- a = p(B), B = c.
?- p(a,1,q(A,1)) = p(B,A,q(C,D)).
Can you explain the answers you got back?

Loading up Prolog Programs


Save a copy of the program family.pl KB1 – KB5 locally. Open the file using a text editor such
as notepad or emacs (the latter is recommended, because it helps you to match brackets).
Have a look at the contents of the file and try to work out what the rules and facts state.
Now you need to load this sample program. In Prolog terms, this is "consulting” the file. The
easiest way to do this on Windows is to select "Consult" from the "File" menu and browse to
the file you want. In general, it is also possible to consult a file by giving a query of the
following kind to the Prolog prompt:
?- consult('Filename').
where 'Filename' is a file in the current working directory. The name needs to be included
inside single quotes if it contains non-alphanumeric characters.
Consult the file 'family.pl'. Now try out a couple of queries, such as
?- mother-of(X,william).
?- father-of(charles,X).
?- mother-of(X,Y).

Can you tell what each query above means?

You might find useful the following built-in predicate


?- listing.
Which shows the current program loaded up. The up arrow and down arrow key allow you
to browse through previous queries you had typed. You may edit the query with the
forward and backward arrow.

The edit-load-run Loop


This part of the practical aims at making you acquainted with the loop that Prolog
programmers go through when writing and testing their programs. This is applicable to any
interpreted languages, by the way.

Page 20 of 45 BIT 312 Notes – Logic


Programming
Open the file family.pl using a text editor like emacs or notepad. A word of warning: if you
are editing files in Windows, do not use a text formatter such as Word. Text formatters
include non-ASCII characters in your file and Prolog can only read in ASCII.
Edit the family.pl program and add rules to define the following family relationships:
grandparent(X,Y), which holds if X is a grandparent of Y
ancestor(X,Y), which holds if X is an ancestor of Y
sibling(X,Y), which holds if X is a sibling of Y

The edit-load-run loop is the way you should proceed: edit the file (don't forget to save it!),
then re-load it in Prolog (just as you did above), and try it. If it doesn't work, go back to
editing, save the changes, re-load the file in Prolog and try again. When you are happy with
the results you get, then stop!

Simple I/O in Prolog


Use the write statement
write(‘Hello World’)
write(‘Hello World’), write(‘Welcome to Prolog’)

Use a Newline
write(‘Hello world’), nl, write(‘Welcome to prolog’),nl.

nl stands for ‘start anew line’. Like all other user input, the above line does not have any
effect until the ‘return’ key is pressed.
Doing so produces the following output.

Hello World
Welcome to Prolog
True.
Followed by a further system prompt ? –

Page 21 of 45 BIT 312 Notes – Logic


Programming
Arithmetic Expressions

If you've tried to use numbers in Prolog before, you might have encountered some
unexpected behaviour of the system. The rest part of this section clarifies this phenomenon.
After that an overview of the arithmetic operators available in Prolog is given.

2.1 The is-Operator for Arithmetic Evaluation

Simple arithmetic operators such as + or * are, as you know, valid Prolog atoms. Therefore,
also expressions like +(3, 5) are valid Prolog terms. More conveniently, they can also be
written as infix operators, like in 3 + 5.

Without specifically telling Prolog that we are interested in the arithmetic properties of such
a term, these expressions are treated purely syntactically, i.e., they are not being evaluated.
That means using = won't work the way you might have expected:

?- 3 + 5 = 8.

false

The terms 3 + 5 and 8 do not match – the former is a compound term, whereas the latter is a
number. To check whether the sum of 3 and 5 is indeed 8, we first have to tell Prolog to
arithmetically evaluate the term 3 + 5. This is done by using the built-in operator is. We can
use it to assign the value of an arithmetic expression to a variable. After that it is possible to
match that variable with another number. Let's rewrite our previous example accordingly:

?- X is 3 + 5, X = 8.

X = 8

true

We could check the correctness of this addition also directly, by putting 8 instead of the
variable on the left hand side of the is-operator:

?- 8 is 3 + 5.

Page 22 of 45 BIT 312 Notes – Logic


Programming
true

But note that it doesn't work the other way round!

?- 3 + 5 is 8.

false

This is because is only causes the argument to its right to be evaluated and then tries to match
the result with the left hand argument. The arithmetic evaluation of 8 yields again 8, which
doesn't match the (non-evaluated) Prolog term 3 + 5.

To summarise, the is-operator is defined as follows: It takes two arguments, of which the
second has to be a valid arithmetic expression with all variables instantiated. The first
argument has to be either a number or a variable representing a number. A call succeeds if
the result of the arithmetic evaluation of the second argument matches with the first one (or
in case of the first one being a number, if they are identical).

Note that (in SWI-Prolog) the result of the arithmetic calculation will be an integer whenever
possible. That means, for example, that the goal 1.0 is 0.5 + 0.5 would not succeed, because
0.5 + 0.5 evaluates to the integer 1, not the float 1.0. In general, it is better to use the operator
=:= (which will be introduced in Section 3.2) Instead whenever the left argument has been
instantiated to a number already.

2.2 Predefined Arithmetic Functions and Relations

The arithmetic operators available in Prolog can be divided into functions and relations.
Some of them are presented here.

Functions. Addition or multiplication are examples for arithmetic functions. In Prolog all
these functions are written in the natural way. The following term shows some examples:

2 + (-3.2 * X - max(17, X)) / 2 ** 5

The max/2-expression evaluates to the largest of its two arguments and 2 ** 5 stands for “2 to
the 5th power” (25). Other functions available include min/2 (minimum), abs/1 (absolute
value), sqrt/1 (square root), and sin/1 (sinus).
Page 23 of 45 BIT 312 Notes – Logic
Programming
The operator // is used for integer division. To obtain the remainder of an integer division
(modulo) use the mod-operator. Precedence of operators is the same as you know it from
mathematics, i.e., 2 * 3 + 4 is equivalent to (2 * 3) + 4 etc.

You can use round/1 to round a float number to the next integer and float/1 to convert
integers to floats.

All these functions can be used on the righthand side of the is-operator.

Relations. Arithmetic relations are used to compare two evaluated arithmetic expressions.
The goal X > Y, for example, will succeed if expression X evaluates to a greater number than
expression Y. Note that the is-operator is not needed here. The arguments are evaluated
whenever an arithmetic relation is used.
Besides > the operators < (less than), =< (less than or equal), >= (greater than or equal), =n=
(non-equal), and =:= (arithmetically equal) are available. The differentiation of =:= and = is
crucial. The former compares two evaluated arithmetic expressions, whereas the later
performs logical pattern matching. X = Y, X \= Y, X < Y, X > Y, X =< Y, X >= Y

?- 2 ** 3 =:= 3 + 5.
true

?- 2 ** 3 = 3 + 5.
false

Note that, unlike is, arithmetic equality =:= also works if one of its arguments evaluates to
an integer and the other one to the corresponding float.

Input/Output and File Handling


How to explicitly output data onto the user’s terminal using the write/1 predicate.
Example:

?- X is 5 * 7,write(‘The result is:’),write(X).

The result is: 35

X = 35

true
Page 24 of 45 BIT 312 Notes – Logic
Programming
Now we are also going to see how to read input. In Prolog, input and output from and to the
user is very similar to input and output from and to files, so we are going to deal with these
in one go.

To read from the current input stream, use the predicate read/1. But note that this only
works if the input stream is a sequence of terms, each of which is followed by a full stop (as
in a Prolog program file, for instance).

Example with user input: Write a Prolog program to print out a square of n x n given
numbers on the screen.

start :-

write(‘Enter a number followed by a full stop: ’),

read(Number),

Square is Number * Number,

write(‘Square: ’),

write(Square).

After compilation, it works as follows:

?- start.

Enter a number followed by a full stop: 17.

Square: 289

true

Comparisons

Example

Prince was a prince during year, Year if Prince reigned between years Begin and End, and Year
is between Begin and End.

prince(Prince,Year):- reigns(Prince,Begin,End),Year >= Begin, Year =< End.

Page 25 of 45 BIT 312 Notes – Logic


Programming
reigns(adamu,1844,1878).
reigns(ibrahim,1878,1916).
reigns(godwin_barry,1916,1950).
reigns(samuel_daniel,1950,1979).
reigns(oblete_gin_lumi,1979,1985).
reigns(peter,1985,1986).
reigns(abduganiyu,1986,1999).

Class Activity
 Was Peter a prince in 1986?
 Was Adamu a prince in 1995?
 Who was the prince in 1900?
 Who was the prince in 1979?
 Who was the prince in 1995?

LOGIC PROGRAMMING IN LISP


Brief Background
The name LISP stands for List Processor. Common Lisp is the modern descendant of the
Lisp language first conceived by John McCarthy in 1956. Lisp circa 1956 was designed for
"symbolic data processing" and derived its name from one of the things it was quite good at:
LISt Processing. McCarthy was (and still is) an artificial intelligence (AI) researcher, and
many of the features he built into his initial version of the language made it an excellent
language for AI programming. During the AI boom of the 1980s, Lisp remained a favorite
tool for programmers writing software to solve hard problems such as automated theorem
proving, planning and scheduling, and computer vision. These were problems that required
a lot of hard-to-write software; to make a dent in them, AI programmers needed a powerful
language, and they grew Lisp into the language they needed. And the Cold War helped--as
the Pentagon poured money into the Defense Advanced Research Projects Agency (DARPA),
a lot of it went to folks working on problems such as large-scale battlefield simulations,
automated planning, and natural language interfaces. These folks also used Lisp and
continued pushing it to do what they needed.

Features of Common LISP


• It is machine-independent
• It uses iterative design methodology, and easy extensibility.
• It allows updating the programs dynamically.
• It provides high level debugging.
• It provides advanced object-oriented programming.
• It provides convenient macro system.
• It provides wide-ranging data types like, objects, structures, lists, vectors, adjustable
arrays, hash-tables, and symbols.
• It is expression-based.
• It provides an object-oriented condition system.
Page 26 of 45 BIT 312 Notes – Logic
Programming
• It provides complete I/O library.
• It provides extensive control structures.

Applications Built in LISP


Large successful applications built in Lisp.
• Emacs
• AI Robots
• AutoCad
• Computer Games (Craps, Connect-4, BlackJack)
• Yahoo Store

A Simple Program
Let us write an s-expression to find the sum of three numbers 7, 9 and 11. To do this, we can
type at the interpreter prompt.

CL-USER > (+ 7 9 11)

LISP returns the result:

27

If you would like to run the same program as a compiled code, then create a LISP source
code file named myprog.lisp and type the following code in it.

(write (+ 7 9 11))

When you click the Execute button, LISP executes it immediately and the result returned is:

27

This was a list of four items: the symbol +, and the numbers 7, 9, and 11. When Lisp
evaluates a list it treats the first item as the name of a procedure, and the remaining items as
the arguments to the expression. In prefix notation, operators are written before their
operands.

Expressions

Let's look a bit more closely at expressions.

In Lisp, + is a procedure, and an expression like (+ 2 3) is a procedure call. When Lisp


evaluates a procedure call it performs the following two steps:

• It evaluates the arguments, left to right. In this case the arguments are just the
numbers 2 and 3, and these evaluate to themselves.

Page 27 of 45 BIT 312 Notes – Logic


Programming
• The values of the arguments are passed to the procedure, in this case +, which returns
5.

Let's look at a more complicated example: (/ (- 7 1) (- 4 2)). The sequence is:

• Evaluate (- 7 1) giving 6

• Evaluate (- 4 2) giving 2

• Evaluate (/ 6 2) giving 3

• Return 3

Preventing evaluation: quote

Nearly all operators behave like this, but there are some special operators that behave
differently. One is quote. Try

CL-USER > (quote (+ 2 3))

(+ 2 3)

The quote operator doesn't evaluate its argument - it simply returns it. It lets you tell Lisp to
treat an expression as data, rather than something to be evaluated.

For convenience you can abbreviate (quote something) to 'something. Try:

CL-USER > '(+ 2 3)

(+ 2 3)

The ' operator allows you to protect an expression from evaluation. Now try quoting one of
the arguments, as in:

CL-USER > (list '(* 1 2) (* 3 4))

((* 1 2) 12)

The quote stopped the first argument from being evaluated.

Evaluating expressions: eval

The opposite of quote is eval; it evaluates the expression passed as its argument. So:

CL-USER > (eval '(+ 2 3))

5
Page 28 of 45 BIT 312 Notes – Logic
Programming
Defining Procedures

So far we've just used the Listener as a calculator, to evaluate expressions. In this lesson
we're going to make a huge leap forward and show you how to define your own procedures.
Once you've defined a procedure, it has the same status as the built-in procedures, like list
and +.

Let's define a procedure to return the average of two numbers.

In words, the procedure for finding the average of two numbers is:

• Add the first number to the second number.

• Divide the sum by 2.

Defining a procedure: defun

To define a procedure you use the special operator defun. This is an abbreviation for define
function, but Lisp procedures are not strictly functions in the mathematical sense.

We can write the average procedure as follows:

(defun average (1st-number 2nd-number)

(/ (+ 1st-number 2nd-number) 2))

The first argument to defun gives the name of the procedure, which we've chosen as
average.

The second argument, (1st-number 2nd-number), is a list of what are called the
parameters. These are symbols representing the numbers we are going to refer to later in the
procedure.

The rest of the definition, (/ (+ 1st-number 2nd-number) 2), is called the body of the
procedure. It tells Lisp how to calculate the value of the procedure, in this case sum the
numbers and divide by 2.

The words you use for the parameters are arbitrary, as long as they match the words you use
in the procedure definition. So you could equally well have written the average procedure as
follows:

(defun average (a b)

(/ (+ a b) 2))

To define it we can type the definition at the Lisp prompt:

Page 29 of 45 BIT 312 Notes – Logic


Programming
CL-USER > (defun average (1st-number 2nd-number) (/ (+ 1st-number 2nd-
number) 2))

AVERAGE

Alternatively you could type the definition into the Lisp Editor and select Compile Buffer to
evaluate it.

We can try it out with:

CL-USER > (average 7 9)

or:

CL-USER > (average (+ 2 3) (+ 4 5))

Remember that the arguments are evaluated before they are given to the procedure.

Variables

Let's consider how you would write a function to convert from Naira to Dollars. The
procedure is as follows:

• Multiply the number of naira by the exchange rate.

The exchange rate is the number of dollars in 1 naira; let's say today it's 0.004023. So our
procedure definition is simply:

(defun convert (naira) (* naira 0.004023))

We call it as follows:

CL-USER > (convert 100)

.4023

In the definition of convert, the symbol naira is a variable. It's like a box into which we put
any value we want to convert to dollars.

Furthermore, it's called a local variable because the value is only available inside the body of
the function. If we try and evaluate dollars outside the procedure we get:

CL-USER > naira

Page 30 of 45 BIT 312 Notes – Logic


Programming
Error: The variable NAIRA is unbound.

Unbound is another way of saying that it doesn't have a value.

Defining a variable: defparameter

But one problem with convert is that we have to redefine it if the exchange rate changes.
What we need is a variable that we can set to the current exchange rate. We can do that with
defparameter:

(defparameter exchange-rate 0.004023)

Variables defined by defparameter are called global variables because you can refer to them
anywhere.

CL-USER > exchange-rate

0.004023

So now we can redefine convert to use the exchange-rate variable:

(defun convert (naira) (* naira exchange-rate))

A common convention is to give each global variable a name starting and ending with an
asterisk, such as *exchange-rate*, to remind you that it's global.

Changing the value of a variable: setf

To change the value of a variable we can use setf. For example, if the exchange rate changes
to 0.005023:

(setf exchange-rate 0.005023)

Now the convert procedure will automatically reflect the new exchange rate:

CL-USER > (convert 100)

0.502299

Creating local variables: let*

The let* construct allows you to define one or more variables, with specified initial values,
which are local to the body of the let* construct. It is written like this:

(let* ((var1 value1)

Page 31 of 45 BIT 312 Notes – Logic


Programming
(var2 value2)

...)

body)

where:

• var1 is the first local variable and value1 is its initial value.

• var2 is the second local variable and value2 is its initial value, and so on.

• body is one or more Lisp procedures that can refer to var1 and var2.

As an example of its use, remember our definition of the average procedure:

(defun average (1st-number 2nd-number)

(/ (+ 1st-number 2nd-number) 2))

The parameters 1st-number and 2nd-number are local variables, local to the body of the
procedure.

But suppose we wanted to break the calculation into steps, using a local variable sum to store
the sum of the two numbers, and another local variable result to store the result of dividing
this by 2.

We could do this with let*:

(defun average (1st-number 2nd-number)

(let* ((sum (+ 1st-number 2nd-number))

(result (/ sum 2)))

result))

Note that the asterisk in let* is part of the name - let* is a more general version of an operator
called let; we won't worry about the differences at this stage.

Manipulating Lists

We've already seen the procedure list that constructs a list out of several items. Here are
some more procedures for working with lists.

Page 32 of 45 BIT 312 Notes – Logic


Programming
Returning the first element of a list: first

The procedure first returns the first element of a list:

CL-USER > (first '(23 34 45))

23

There are similar procedures second, third, and so on up to tenth for getting later items in a
list.

Returning all but the first element of a list: rest

The procedure rest takes a list and returns the list minus the first element:

CL-USER > (rest '(23 34 45))

(34 45)

Note that first and rest were traditionally called car and cdr; you may see references to these
alternative names in Lisp.

Finding the length of a list: length

The procedure length returns the length of a list; for example:

CL-USER > (length '(1 2 3))

Constructing lists: cons

The procedure cons takes two parameters - an object and a list - and returns a new list with
the object added to the front of the list. For example:

CL-USER > (cons 1 '(2 3 4 5 6))

(1 2 3 4 5 6)

Note that the first object can itself be a list:

CL-USER > (cons '(0 1) '(2 3 4 5 6))

((0 1) 2 3 4 5 6)

Joining lists: append

The procedure append takes any number of lists, and joins them all together into a single list.
For example:
Page 33 of 45 BIT 312 Notes – Logic
Programming
CL-USER > (append '(1 3 5 7) '(2 4 6 8))

(1 3 5 7 2 4 6 8)

Reverse a list: reverse

The procedure reverse takes a list and reverses it:

CL-USER > (reverse '(1 2 3 4))

(4 3 2 1)

Strings

So far we've learnt about the following types of Lisp object:

• Numbers, like 2, 4, and 17.

• Procedure names, like + and list

• Lists, like (1 2 3 4) and (+ 3 6).

We also learnt that Lisp uses lists for both data and programs. The first list (1 2 3 4) is data,
because 1 isn't the name of a procedure. The second list, (+ 3 6), is a procedure call to add 3
and 6 (or it could be data that just happens to look like that procedure call).

Strings

The new type is a string: a sequence of any characters enclosed in double-quotes. So here are
four strings:

"cat"

"dog"

"Once upon a time there was a prince"

""

The last one is the empty string, which contains no characters, but it’s still a valid and useful
string. You can include a double-quote in a string by prefixing it with a backslash:

"He shouted \"Help!\" and ran away." For example

(write-line "Welcome to Bit312 Class")

;escaping the double quote character

(write-line "Welcome to \"Bit312 Class\"")


Page 34 of 45 BIT 312 Notes – Logic
Programming
When you execute the code, it returns the following result:

Welcome to Bit312 Class

Welcome to "Bit312 Class"

Now some procedures that work with strings (note that most of these also work with lists):

Adding Comments

The semicolon symbol (;) is used for indicating a comment line.

For Example,

(write-line "Hello World") ; greet the world

; tell them your whereabouts

(write-line "I am at 'Bit312 Class'! Learning LISP")

When you click the Execute button, LISP executes it immediately and the result returned is:

Hello World

I am at 'Bit312 Class'! Learning LISP

Finding the length of a string: length

We've already met length with lists. It can also be used to find the length of a string; for
example:

CL-USER > (length "Lisp")

Reversing a string: reverse

The procedure reverse also works for strings:

CL-USER > (reverse "dog")

"god"

Joining two (or more) strings together: concatenate

The second argument has to be 'string to tell the procedure what type of thing you're
concatenating:

Page 35 of 45 BIT 312 Notes – Logic


Programming
CL-USER > (concatenate 'string "band" "age")

"bandage"

The concatenate procedure can take an arbitrary number of strings and they will all be joined
into one long string.

Getting a subsequence from a string: subseq

This procedure extracts part of a string. It takes three parameters:

• the string

• the number of the first character you want

• the number of the character one after the last character you want

The characters are counted starting at 0. For example:

CL-USER > (subseq "averylongword" 5 9)

"long"

If you leave out the third parameter you get the rest of the string to the end.

Printing

When you evaluate a Lisp procedure in the Listener it returns a value. But when you're
running a program you might want it to print out several values before the program returns
a final result. Lisp provides several alternative functions specifically for printing out values.

Printing a result: print

The simplest of these is print - it simply prints out a printed representation of its argument,
and then returns the argument. So if you evaluate, for example:

CL-USER > (print 123)

123

123

The first "123" is the effect of the print procedure. The second "123" is the value returned by
the procedure, which is also 123. This example makes it a bit clearer:

(defun print-and-double (n) (print n) (* n 2))

If we evaluate it we get:

Page 36 of 45 BIT 312 Notes – Logic


Programming
CL-USER > (print-and-double 12)

12

24

Printing formatted values: format

The Swiss army knife of printing is format. It includes options for printing every type of
value in every conceivable way, and we would guess that most Lisp programmers only use a
small subset of its capabilities. We'll just cover its most useful features here:

The format procedure takes two or more parameters.

The first parameter is either t, to tell the format procedure to print the result, or nil, to return
the result as a string.

The second parameter is a format string, which tells the format procedure how to print the
result. This is a text string which can include special format sequences, prefixed by a "~"
character (called a "tilde" or "twiddle"), to insert values in this string.

The remaining parameters are evaluated to give the values to be inserted into the format
string. The most general format sequence is "~a" which inserts the value as it would be
printed by print. So, for example:

(format t "The answer is ~a." (* 2 3))

inserts the value of (* 2 3) into the string specified by the ~a, and prints:

The answer is 6.

You can also include ~% in the format string to give a line break.

Alternatively, by specifying the second parameter as nil we can use format to generate a
string for us, so:

(format nil "The answer is ~a." (* 2 3))

will return:

"The answer is 6."

Testing a Result

Page 37 of 45 BIT 312 Notes – Logic


Programming
An important tool in writing programs is to be able to perform different actions depending
on the result of a calculation.

First we need to introduce some functions for testing the value of an expression. Lisp
includes a number of functions that give a true or false answer - they are called predicates,
and often (but not always) have names ending with p.

In Lisp the convention is that nil, or the empty list, means false, and anything else means
true. If you want to represent true you use the special Lisp operator, t.

Testing Lisp objects: eq

The most fundamental Lisp test is eq; it tests whether two Lisp objects are identical. For
example:

CL-USER > (eq 'fred 'fred)

CL-USER > (eq 'fred 'jim)

NIL

Testing numbers: =, >, and <

There are several functions for testing numbers. For example = tests whether two or more
numbers are equal:

CL-USER > (= 2 2)

CL-USER > (= 2 3)

NIL

Likewise, > tests if the first number is greater than the second:

CL-USER > (> 4 3)

Remember, Lisp uses prefix notation, so the function comes first. In normal maths notation
this means 4 > 3.

CL-USER > (> 3 3)

NIL
Page 38 of 45 BIT 312 Notes – Logic
Programming
The eq test can also be used for numbers, but it's more intuitive to use =.

Are numbers even or odd: evenp, oddp

You can test whether a number is even or odd with evenp and oddp. For example:

CL-USER > (evenp 17)

NIL

CL-USER > (oddp 17)

Testing whether strings are equal: string=

Doesn't need much explanation:

CL-USER > (string= "cat" "cat")

CL-USER > (string= "cat" "dog")

NIL

Is something a number? numberp

You can test whether something is a number with numberp. So:

CL-USER > (numberp 2)

CL-USER > (numberp '(1 2 3))

NIL

Conditional test: if

Now we're ready to use these procedures to perform different actions depending on the
value of an expression. Lisp includes several different forms to do this, but the simplest is if.

The form if takes three parameters. It evaluates the first parameter; if it returns a non-nil
value, it evaluates and returns the second parameter; otherwise it evaluates and returns the
third parameter.

Page 39 of 45 BIT 312 Notes – Logic


Programming
Combining tests: and, or, not

You can combine several tests using the procedures and, or, and not.

or can take any number of parameters, and only returns nil if all its parameters evaluate to
nil; otherwise it returns T.

and can take any number of parameters, and only returns T if all its parameters evaluate to
T; otherwise it returns nil.

not takes one parameters, and returns T if its parameter evaluates to nil; otherwise it returns
nil.

For example, to test whether someone is a teenager:

(defun teenager (age)

(if (and (> age 12) (< age 20))

"Teenager"

"Not teenager"))

Creating a Sequence

The function make-sequence allows you to create a sequence of any type. The syntax for this
function is:

make-sequence sqtype sqsize & key :initial-element

It creates a sequence of type sqtype and of length sqsize.

You may optionally specify some value using the :initial-element argument, then each of the
elements will be initialized to this value.

For example, Create a new source code file named main.lisp and type the following code in
it.

(write (make-sequence '(vector float)

10

:initial-element 1.0))

When you execute the code, it returns the following result:

#(1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0)

Page 40 of 45 BIT 312 Notes – Logic


Programming
Modifying Sequences

Some sequence functions allows iterating through the sequence and perform some
operations like, searching, removing, counting or filtering specific elements without writing
explicit loops.

The following example demonstrates this:

Example 1

Create a new source code file named main.lisp and type the following code in it.

(write (count 7 '(1 5 6 7 8 9 2 7 3 4 5)))

(terpri)

(write (remove 5 '(1 5 6 7 8 9 2 7 3 4 5)))

(terpri)

(write (delete 5 '(1 5 6 7 8 9 2 7 3 4 5)))

(terpri)

(write (substitute 10 7 '(1 5 6 7 8 9 2 7 3 4 5)))

(terpri)

(write (find 7 '(1 5 6 7 8 9 2 7 3 4 5)))

(terpri)

(write (position 5 '(1 5 6 7 8 9 2 7 3 4 5)))

When you execute the code, it returns the following result:

2
(1 6 7 8 9 2 7 3 4)
(1 6 7 8 9 2 7 3 4)
(1 5 6 10 8 9 2 10 3 4 5)
7
1

Sorting and Merging Sequences

The sorting functions take a sequence and a two-argument predicate and return a sorted
version of the sequence.

Example 1

Page 41 of 45 BIT 312 Notes – Logic


Programming
Create a new source code file named main.lisp and type the following code in it.

(write (sort '(2 4 7 3 9 1 5 4 6 3 8) #'<))


(terpri)
(write (sort '(2 4 7 3 9 1 5 4 6 3 8) #'>))
(terpri)

When you execute the code, it returns the following result:

(1 2 3 3 4 4 5 6 7 8 9)
(9 8 7 6 5 4 4 3 3 2 1)

Example 2

Create a new source code file named main.lisp and type the following code in it.

(write (merge 'vector #(1 3 5) #(2 4 6) #'<))


(terpri)
(write (merge 'list #(1 3 5) #(2 4 6) #'<))
(terpri)
When you execute the code, it returns the following result:

#(1 2 3 4 5 6)
(1 2 3 4 5 6)

Mapping Sequences

We have already discussed the mapping functions. Similarly the map function allows you to
apply a function on to subsequent elements of one or more sequences.
The map function takes a n-argument function and n sequences and returns a new sequence
after applying the function to subsequent elements of the sequences.

Example
Create a new source code file named main.lisp and type the following code in it.

(write (map 'vector #'* #(2 3 4 5) #(3 5 4 8)))

When you execute the code, it returns the following result:

#(6 15 16 40)

Creating Dialogue Boxes

In this lesson you are going to learn about how to use Lisp to create Macintosh or Windows
interface features like windows and dialogue boxes.

Page 42 of 45 BIT 312 Notes – Logic


Programming
The procedures to do this are all provided in a LispWorks library called CAPI, and their
names start with capi:.

Displaying a message: capi:display-message

To display a message use the capi:display-message procedure. This is followed by a


format string and a list of arguments, just like format. For example:

(capi:display-message "The sum of ~a and ~a is ~a." 3 4 (+ 3 4))

will display:

Prompting for a string: capi:prompt-for-string

This asks the user to enter a string. For example:

(capi:prompt-for-string "Think of an animal:")

will display:

and will return the string you type in.

Prompting for a number: capi:prompt-for-number

In a similar way:

(capi:prompt-for-number "How many legs does it have:")

Page 43 of 45 BIT 312 Notes – Logic


Programming
will display:

and return the number you type in.

Asking yes or no: capi:prompt-for-confirmation

The procedure capi:prompt-for-confirmation asks a question, and lets the user answer yes


or no:

(capi:prompt-for-confirmation "Are you hungry?")

This displays:

If the user clicks Yes the procedure returns T (true). If the user clicks No it returns Nil (false).

Giving the user a choice: capi:prompt-with-list and capi:prompt-for-items-from-list

Finally, the function capi:prompt-with-list takes a list and a message, and lets the user select
one of the items. For example:

(capi:prompt-with-list

'("red" "blue" "green" "pink")

"What's your favourite colour?")

will display:

Page 44 of 45 BIT 312 Notes – Logic


Programming
and return the value of the item you've selected.

The procedure capi:prompt-for-items-from-list is identical, except that it allows you to select


any number of items, and it returns a list of the item.

Page 45 of 45 BIT 312 Notes – Logic


Programming

You might also like