Prolog Under the Hood

An Honest Look

[This article was originally published in PC AI magazine, Sep/Oct 1992. The magazine can be reached at PC AI, 3310 West Bell Rd., Suite 119, Phoenix AZ, USA 85023 Tel: (602) 971-1869, FAX: (602) 971-2321, E-Mail: info@pcai.com, Web: http://www.pcai.com]

Prolog is an exciting and powerful programming language, ideal for most AI applications and for many non-AI applications. After a brief exposure, however, many programmers fail to appreciate its full breadth and never use it again. This article examines the inner workings of Prolog, with an eye on widening the language's appeal.

Introduction

Great Expectations

On the other hand, if the user develops an inaccurate conceptual model, then things do not work as expected. Experiments fail and frustration sets in. Without a way to get a good understanding of how the application works, the user eventually loses interest no matter how slick the interface.

How the application works is not as important for user success as whether it works as expected. The goal of the user interface, then, should be to set the user expectations correctly. In other words, the user interface should teach the user how the application works.

With this in mind, we can see why a language like C is so addictive. As with any language, the syntax is the interface. C's syntax leads the developer to a good conceptual model of how C works. C does its business by generating machine code instructions for a CPU that manipulates memory. The syntax brings this out by including

the declaration of variables that lead to an understanding of memory,

sequences of coded instructions that lead to an understanding of the sequential nature of the CPU, and

constructs (such as pointers) that lead to an understanding of the differences between addresses and values.

The design of low-level programming languages clearly reveals the computer underneath. Assembler does a wonderful job of this. That's why so many programmers are drawn to play with it at some time in their careers.

Low-Level Languages Have Problems, Too

Although low-level languages give a satisfying sense of how a computer works, they are often not very good at expressing the solution to the problem to be solved. The programmer's job is to map the problem's solution into the programming language.

If the problem involves straight-forward 'processing' of 'data', the solution maps relatively easily into the way a computer works, which is it has a processor that manipulates data. But, if the problem is more complex, mapping the solution into the code becomes more difficult.

The Purpose of High-Level Tools...

...And The Problem with High-Level Tools

But, because the tool's user interface usually emphasizes the tool's high-level aspects, the inner workings are often deliberately hidden.

Keeping the inner workings under wraps leads to frustration with learning a high-level tool. The documentation, the hype, and the syntax of the language are all geared to create in the developer a conceptual model of the higher-level view, and deliberately lead the developer away from a good conceptual model of how the tool actually works.

It's no wonder, then, that many programmers go back to the old way of doing things after a brief exposure to these tools. They promise to let you program at a higher level. Then they don't work as expected.

Prolog

How Prolog Really Works

Prolog is much better understood as a language with two unusual features--unification and backtracking. (It also makes heavy use of recursion, which is more common than unification and backtracking but difficult to grasp if you haven't encountered it before.)

Prolog's interface, however (along with much of the Prolog literature), deliberately leads the developer into a conceptual model of logic--and away from Prolog's true inner workings. The syntax of the language is derived from Horn clauses (an area of logic), and early teaching examples emphasize the Prolog-logic connection.

Questions and Answers

human(socrates). % facts about who is human human(aristotle). human(plato). god(zeus). % and who is a god god(apollo). mortal(X) :- human(X). % a logical assertion that X is mortal if X is %human

?-mortal(plato). % is Plato mortal? yes ?-mortal(apollo). % is apollo mortal? no ?-mortal(X). % for which X is X mortal? X = socrates ->; X = aristotle ->; X = plato ->; no

Toward Real Applications

There isn't a clue in the language that the following program does the job:

mortal_report :- write('Report of all known mortals'), nl, nl, mortal(X), write(X), nl, fail. mortal_report.

?- mortal_report. Report of all known mortals socrates aristotle plato yes

Backtracking and Unification

First, we need to understand that when we pose a query to Prolog at the ?- prompt we are asking it to see if the pattern of the query matches any patterns in the program. The way it matches patterns is called unification. The way it searches for patterns is called backtracking.

Let's look at the mortal program again. The query ?- human(socrates). will cause Prolog to search for that pattern. Prolog treats each query as a goal. Finding its goal, Prolog responds yes. Similarly ?- human(zeus). will cause 'no' to appear.

If we give it the query ?- human(X). Prolog will search for patterns that can match the query pattern if X takes on certain values. In our example this will work for X = socrates. So Prolog responds X = socrates ->. The arrow is the clue that backtracking is part of Prolog. Unlike in logic, Prolog did not find all the values of X for which the query pattern is true. It just found the first one. The -> indicates there may be more. Hitting ; tells Prolog to go look for more. In response, Prolog unbinds the variable X from the value socrates, continues searching, and responds X = aristotle ->. Again, the ; tells Prolog to look for more solutions, and we get X = plato ->. Asking for still more produces 'no' because there are no more solutions.

Backtracking is the repeated searching for additional solutions, so named because Prolog goes back and tries again to find a solution. Unification is the binding of X with each name in succession.

In the more complex example of the goal ?- mortal(X). Prolog matches the patterns with the rule mortal(X) :- human(X) (which means "X is a mortal if X is human"). Prolog then reduces the search for solutions to mortal(X) to a search for solutions to human(X), and the queries work as before.

Prolog's unification pattern-matching algorithm is much more powerful than this simple example can show, and it can be used to express elegant solutions to many complex problems. Backtracking search quickly converges on solutions without the need for the developer to code the flow of control structures.

In other words, an awful lot goes on behind the scenes when Prolog responds to the simple query pattern human(X). Figure 1 schematically shows the full procedurality. First the goal, human(X) is called, and Prolog tries to match the pattern with the known facts. That is the significance of the upper left diamond. If Prolog succeeds in finding a match it exits, binding X to the value. If Prolog fails it prints no.



Figure 1: A schematic diagram showing how Prolog responds to a query



If the user asks Prolog to try again (by typing ;), the goal human(X) is reentered but conceptually from the other side. This is backtracking. The lower right diamond is another decision point. It indicates that if another fact is found that matches the pattern, rebind X to it and exit again. If not, the attempted pattern match fails.

Built-In Predicates

Given the underlying procedural nature of Prolog, it is natural for the programmer to want to exercise control. For our example, one possibility is to loop though all mortals to print a report. A number of predicates provide straightforward control over backtracking. The one in the example is fail. It always fails when called. Figure 3 shows the flow of control though fail.



Figure 3: Flow of control through fail



The Report Program Revisited

human_report :- write(heading), human(X), write(X), fail.

Less Glamor, More Beauty

How many applications can benefit from automatic pattern-matching and backtracking search? Just about anything you'd want to do in AI and more.

For example, consider the problem of building a forward-chaining (data-driven) inference engine. This is useful for building several types of complex expert systems, such as configuration and scheduling systems. The inference engine goes though a cycle of looking for rules whose conditions are met, and then taking the appropriate actions.

In C, or some other low-level language, a developer would have to build

pattern-matching for the conditions

execution logic for the actions

search mechanisms to find the matching rules

a looping procedure.

forward_chain :- rule(conditions(X), actions(Y)), call(X), call(Y), forward_chain.

Just about anything you want to do in AI requires pattern-matching and search, and that is what Prolog does best. Natural language parsing, game playing, expert system building, frame and object implementation, simulations--all are easy in Prolog.

Further, many conventional applications (like pricing, order processing, inventory checking, accounting transactions, and tax computations) also involve pattern-matching and search. These too are pratical and enjoyable to implement in Prolog.

Once a developer grasps a good conceptual model of how Prolog really works, all of the language's possibilities become clear.

Conclusion

Prolog, billed as "logic programming", is not really. You may be disappointed if that's what you expected to find. On the other hand, having backtracking, unification, and recursion inside one computer language leads to something very powerful and special.

Maybe if it was clearer exactly how tools such as Prolog worked, many programmers would be addictively drawn to them, as they now are to lower-level languages. The challenge to our industry is to design user interfaces that, instead of shielding a practical view of the tool, bring such a view to light. Then we will begin to draw more people in. For now, those interested in Prolog need to find a good book and perform a lot of experiments.

Until we start developing more intuitive interfaces, the tools of the trade will remain in the hands of those who have the perseverance to dig out an understanding of what's hidden beneath the surface.

In the case of Prolog, there is a special reward for that perseverance: It really is sort of like programming in logic.

Reference

Dennis Merritt is the author of many Prolog books and articles and president of Amzi! inc.