Prolog

Table of Contents

Knowledge databases that you might build with
first-order logic suffer from a
fatal problem: the logic is so sophisticated that finding answers
efficiently (or even finding an answer at all) becomes difficult or
impossible. First-order logic is so powerful that it can express almost
anything, including very tricky or paradoxical statements. It's not easy
to design algorithms to work with such systems.

Prolog is a programming language that allows us to "program" with
declarative knowledge. It puts limitations on the kinds of logical
statements we can write. These limitations are essential for allowing
Prolog programs to work efficiently and always provide an answer (or
determine there is no answer).

Because Prolog is a "Turing-complete" language, we can write any program
in Prolog. Of course, a program that is easy to write in Java or C++ will,
in all likelihood, be very difficult to write in Prolog, although it
is possible. Prolog programs often feel like they're programs written in
"reverse," since they are written declaratively rather than procedurally.

(Note: Download and install
SWI Prolog; you may
also want PDT
for Eclipse. The CSE Linux servers do not have Prolog; that is
ok, you should still submit from Linux but otherwise test your
code on your own machine. There is also the option to download a
portable version of SWI Prolog
to install on a USB key and use in any Windows machine you'd like,
such as the campus lab computers.)

Some quick concepts and examples

There is probably no better way to introduce Prolog than to show some
examples. But first, a few important notes:

Prolog programs have two parts: a database (of facts and rules),
and an interactive "query" tool; the database must be typed into a
file.

Prolog databases are "consulted" (loaded), and then the query tool
is used to "make queries" (ask questions) about the database.

How queries are answered is generally beyond the control of the
programmer; Prolog uses a depth-first search to figure out how to
answer queries; this is (generally) non-negotiable.

So Prolog has found an assignment for X that makes the statement
true. We call parent a predicate (a 2-ary predicate because it
accepts two inputs), and we always write queries by first stating a
predicate.

If I don't actually care who the children are, I can replace C with
_ like so:

?- parent(P, _).
P = tom ;P = bob ;P = bob ;P = pat.

We get bob twice because he's the parent of two people.

Now let's ask, "Who are the female parents?"

?- parent(P, _), female(P).
P = pat.

The comma means "AND." So the query requires that P is a parent and
a female.

Defining complex predicates (rules)

The predicates shown above (parent, male, and female) are always
true. They are called "facts."

A "rule," instead, is true only when some conditions are met. These
conditions look like the last query above: they typically require
several predicates to be true. The last query above is basically what
defines a "mother." Let's make a rule (in our database file,
family.pl):

mother(X) :- parent(X, _), female(X).

Now for fathers. In the database, we add:

father(X) :- parent(X, _), male(X).

Why the false at the end? The ; basically means "throw away the
answer you just gave, and try again." Prolog finds a parent X
(probably pat), but then that X does not satisfy the second part
of the father rule, which is written male(X), so the answer is
false for that X.

Notice that pat is the parent of jim and bob is the parent of
pat. That makes bob a grandparent. Let's write a query to that
effect:

Notice how they all have the same form. Essentially, the variables in
the rule (X or both X and Y in each of these cases) have a
\(\forall\) in front (because the rule is true for all variables X or
X and Y, should certain conditions hold), and the variables inside
the rule have \(\exists\) in front because you only need to find one
value for each variable to make the rule true. Notice we have
\(\rightarrow\) not \(\leftrightarrow\) because there may be more than one
way to satisfy a rule. For example,

This is how we do "or" in Prolog: we make new rules. In each rule, we
use commas for "and," but (generally) do not write "or" inside a rule.

Negation as failure

Negation is a little bit funny in Prolog. That's because negation is a
little bit funny in life. How does anyone prove a negative statement?
"I never read that book" — how can that be proved?

Let's say we want a predicate like "this person is not a father."
Well, we know how to figure out who is a father: we have the
father rule. How can we define a "is not a father" rule?

The first thing to note is Prolog does not allow us to use negatives
in facts. We cannot write "not father(jim)."

Instead, to prove a negative in Prolog, we use a little trick: we
assume everything stated in the database is everything that exists
(this is the "closed-world assumption"). Anything not stated in the
database does not exist. So, we can "prove" that jim is not a father
by failing to prove that jim is a father (with our father rule).

not_a_father(X) :- \+father(X).

Some queries:

?- not_a_father(bob).
false.
?- not_a_father(jim).
true.

The \+ is the "negation operator." The idea is that \+father(X) is
true whenever father(X) fails to be proved (recall that Prolog may
search every possibility in order to prove something; thus, negation
can be computationally expensive).

We cannot use \+ on the left-side of a rule or written as a fact
because it makes no sense to say, "this will fail to be proven
always." Only positive statements can be on the left of a rule or
written as a fact.

This connection between "failing to prove" and "negation" is called
"negation as failure." Negation as failure means, "proving the
negation of something is equivalent to failing to prove that
something" and depends on a closed-world assumption.

negation-as-failureAlsonegation-by-failuren.Logic
programming1 The reasonable assumption that P is false if one
has failed to prove that P is true. 2 The unreasonable assumption
that P is false if others have failed to prove that P is true. 3Drugs The ineffectiveness of the "Just Say No!" campaign. — The
computer contradictionary

Lists in Prolog

Besides atoms (ann, pat, bob, etc.) we can create lists. Let's
make a simple predicate that says whether or not something is at the
head (front) of a list:

head(X, [X|_]).

The code [X|_] means there is a list that starts with X and the
rest (after the |) I don't care about (hence the _). Here are some
uses of this predicate:

The _G228 is a variable that Prolog created to represent the rest of
the list; it has made no commitment about what _G228 is, hence it
can only describe it as variable _G228.

Predicates can be recursive, which is exactly what is needed for the
membership predicate:

% X is a member of a list if the list only has the element X in itmember(X, [X]).
% X is a member of a list if the list starts with X% (technically, the above statement is not needed; this% statement suffices)member(X, [X|_]).
% Finally, X is a member of a list if it's a member of% the tail of the listmember(X, [_|Tail]) :- member(X, Tail).

Example usage (I'm typing . after some responses because I don't
want other possible answers):

In that last case, Prolog is generating lists that have a as a
member somewhere. Pretty weird, huh? It's finding some way to make the
predicate true.

Prolog for databases

Now we'll investigate using Prolog for representing and answering
queries about data. Let's build a database of people and families. We
want to say that certain people are born on certain dates, have jobs
with particular incomes, are married, have children, etc.

We'll start with a predicate, family, that says a family is made up
of an ID (a number), two parents, and zero-or-more children (a
list). Each person has a first name and last name, birth date, and is
either unemployed or has a job with a particular salary.

There are two families. Note that person is not a predicate, it's
just a way of writing data. We have no way of asking person(X, Y,
...) because it's not a predicate. To access person data, we have to
use the family predicate. Here are some examples:

Using this family predicate, we just put a variable in a location
that we want to retrieve information about (such as family ID or a
first name / last name of a person), and put _ for all the slots we
don't care about.

Now let's make it a little more interesting. Here is a married
predicate that says if two people are married:

To facilitate further queries, we'll create an exists predicate that
we can use to retrieve details about a person. It just pulls person
information out of the various places it may appear in a family
fact.

We can use an interesting built-in predicate, findall, that finds a
list of objects that match some "goal." Here is its use in a query:

% First part of findall is what to collect,% second part is the "goal" (a predicate),% third part is the name of the resulting list
?- findall(FirstName, exists(person(FirstName, _, _, _)), Result).
Result = [tom, susan, ann, jess, pat, jim, amy, ace].