Compound Data Objects and Functors

Compound data objects allow you to treat several pieces of information as a single item in such a way that you can easily pick them apart again. Consider, for instance, the date April 2, 1988. It consists of three pieces of information--the month, day, and year--but it's useful to treat the whole thing as a single object with a treelike structure:

          DATE
  
    /      |      ¡¬
October
  15   1991

You can do this by declaring a domain containing the compound object date:

DOMAINS
    date_cmp = date(string,unsigned,unsigned)

and then simply writing e.g.

    ..., D = date("October",15,1991), ...

This looks like a Prolog fact, but it isn't here--it's just a data object, which you can handle in much the same way as a symbol or number. It begins with a name, usually called a functor (in this case date), followed by three arguments.

Note carefully that a functor in Visual Prolog has nothing to do with a function in other programming languages. A functor does not stand for some computation to be performed. It's just a name that identifies a kind of compound data object and holds its arguments together.

The arguments of a compound data object can themselves be compound. For instance, you might think of someone's birthday as an information structure like this:

           BIRTHDAY
 
          /          ¡¬
         /              ¡¬
   person
             date
    /
      ¡¬          /    |  ¡¬
"Per"
     "Bilse" "Apr" 14 1960

In Prolog you would write this as:

    birthday(person("Per","Bilse"),date("Apr",14,1960))

In this example, there are two parts to the compound object birthday: the object person("Per", "Bilse") and the object date("Apr", 14, 1960). The functors of these data objects are person and date.

1. Unification of Compound Objects

A compound object can unify either with a simple variable or with a compound object that matches it (perhaps containing variables as parts of its internal structure). This means you can use a compound object to pass a whole collection of items as a single object, and then use unification to pick them apart. For example,

    date("April",14,1960)

matches X and binds X to date("April",14,1960).

Also

    date("April",14,1960)

matches date(Mo,Da,Yr) and binds Mo to "April", Da to 14, and Yr to 1960.

Some examples of programming with compound objects follow in the next sections.

(1) Using the Equal Sign to Unify Compound Objects

Visual Prolog performs unification in two places. The first is when a call or goal matches the head of a clause. The second is the across the equal (=) sign, which is actually an infix predicate (a predicate that is located between its arguments rather than before them).

Visual Prolog will make the necessary bindings to unify the objects on both sides of the equal sign. This is useful for finding the values of arguments within a compound object. For example, the following code excerpt tests if two people have the same last name, then gives the second person the same address as the first.

/* Program ch05e01.pro */

2. Treating Several Items as One

Compound objects can be regarded and treated as single objects in your Prolog clauses, which greatly simplifies programming. Consider, for example, the fact

    owns(john, book("From Here to Eternity", "James Jones")).

in which you state that John owns the book From Here to Eternity, written by James Jones. Likewise, you could write

    owns(john, horse(blacky)).

which can be interpreted as

    John owns a horse named blacky.

The compound objects in these two examples are

    book("From Here to Eternity", "James Jones")

and

    horse(blacky)

If you had instead written two facts:

    owns(john, "From Here to Eternity").
    owns(john, blacky ).

you would not have been able to decide whether blacky was the title of a book or the name of a horse. On the other hand, you can use the first component of a compound object--the functor--to distinguish between different objects. This example used the functors book and horse to indicate the difference between the objects.

Remember: Compound objects consist of a functor and the objects belonging to that functor, as follows:

    functor(object1, object2, ..., objectN)

(1) An Example Using Compound Objects

A important feature of compound objects allows you to easily pass a group of values as one argument. Consider a case where you are keeping a telephone database. In your database, you want to include your friends' and family members' birthdays. Here is a section of code you might have come up with:

PREDICATES
    phone_list(symbol, symbol, symbol, symbol, integer, integer)
       /* ( First,
   Last,  Phone,  Month,     Day,    Year) */

CLAUSES
    phone_list(ed, willis, 422-0208, aug, 3, 1955).
    phone_list(chris, grahm, 433-9906, may, 12, 1962).

Examine the data, noticing the six arguments in the fact phone_list; five of these arguments can be broken down into two com­pound objects, like this:

          person                           birthday
           /
     ¡¬                         /     |     ¡¬
First Name
  Last Name         Month  Day  Year

It might be more useful to represent your facts so that they reflect these compound data objects. Going back a step, you can see that person is a relationship, and the first and last names are the objects. Also, birthday is a relationship with three arguments: month, day, and year. The Prolog representation of these relationships is

    person(First_name, Last_name)
    birthday(Month, Day, Year)

You can now rewrite your small database to include these compound objects as part of your database.

DOMAINS
    name = person(symbol, symbol)
                 /* (First, Last) */
    birthday = b_date(symbol, integer, integer)
/* (Month, Day, Year) */
    ph_num = symbo
                                 /* Phone_number */

PREDICATES
    phone_list(name, ph_num, birthday)

CLAUSES
    phone_list(person(ed, willis), "422-0208", b_date(aug, 3, 1955)).
    phone_list(person(chris, grahm), "433-9906", b_date(may, 12, 1962)).

In this program, two compound domains declarations were introduced. We go into more detail about these compound data structures later in this chapter. For now, we'll concentrate on the benefits of using such compound objects.

The phone_list predicate now contains three arguments, as opposed to the previous six. Sometimes breaking up your data into compound objects will clarify your program and might help process the data.

Now add some rules to your small program. Suppose you want to create a list of people whose birthdays are in the current month. Here's the program code to accomplish this task; this program uses the standard predicate date to get the current date from the computer's internal clock. The date predicate is discussed later in chapter 15. For now, all you need to know is that it will return the current year, month, and day from your computer's clock.

/* Program ch05e03.pro */

Load and run this program.

How do compound data objects help in this program? This should be easy to see when you examine the code. Most of the processing goes on in the get_months_birthdays predicate.

First, the program makes a window to display the results.

After this, it writes a header in the window to help interpret the results.

Next, in get_months_birthdays, the program uses the built-in predicate date to obtain the current month.

After this, the program is all set to search the database and list the people who were born in the current month. The first thing to do is find the first person in the database. The call phone_list(Person, _, Date) binds the person's first and last names to the variable Person by binding the entire functor person to Person. It also binds the person's birthday to the variable Date.

Notice that you only need to use one variable to store a person's complete name, and one variable to hold the birthday. This is the power of using compound data objects.

Your program can now pass around a person's birthday simply by passing on the variable Date. This happens in the next subgoal, where the program passes the current month (represented by an integer) and the birthday (of the person it's processing) to the predicate check_birthday_month.

Look closely at what happens. Visual Prolog calls the predicate check_birthday_month with two variables: The first variable is bound to an integer, and the second is bound to a birthday term. In the head of the rule that defines check_birthday_month, the first argument, This_month, is matched with the variable Mon. The second argument, Date, is matched against b_date(Month, _,_).

Since all you're concerned with is the month of a person's birthday, you have used the anonymous variable for both the day and the year of birth.

The predicate check_birthday_month first converts the symbol for the month into an integer value. Once this is done, Visual Prolog can compare the value of the current month with the value of the person's birthday month. If this comparison succeeds, then the subgoal check_birthday_month succeeds, and processing can continue. If the comparison fails (the person currently being processed was not born in the current month), Visual Prolog begins to backtrack to look for another solution to the problem.

The next subgoal to process is write_person. The person currently being processed has a birthday this month, so it's OK to print that person's name in the report. After printing the information, the clause fails, which forces backtracking.

Backtracking always goes up to the most recent non-deterministic call and tries to re-satisfy that call. In this program, the last non-deterministic call processed is the call to phone_list. It is here that the program looks up another person to be processed. If there are no more people in the database to process, the current clause fails; Visual Prolog then attempts to satisfy this call by looking further down in the database. Since there is another clause that defines get_months_birthdays, Visual Prolog tries to satisfy the call to get_months_birthdays by satisfying the subgoals to this other clause.

Exercise

Modify the previous program so that it will also print the birth dates of the people listed. Next, add telephone numbers to the report.

3. Declaring Domains of Compound Objects

In this section, we show you how domains for compound objects are defined. After compiling a program that contains the following relationships:

    owns(john, book("From Here to Eternity", "James Jones")).

and

    owns(john, horse(blacky)).

you could query the system with this goal:

    owns(john, X)

The variable X can be bound to different types of objects: a book, a horse, or perhaps other objects you define. Because of your definition of the owns predicate, you can no longer employ the old predicate declaration of owns:

    owns(symbol, symbol)

The second argument no longer refers to objects belonging to the domain symbol. Instead, you must formulate a new declaration to the predicate, such as

    owns(name, articles)

You can describe the articles domain in the domains section as shown here:

    DOMAINS
        articles = book(title,author); horse(name)
                                   /* Articles are books or horses */
        title, author, name = symbol

The semicolon is read as or. In this case, two alternatives are possible: A book can be identified by its title and author, or a horse can be identified by its name. The domains title, author, and name are all of the standard domain symbol.

More alternatives can easily be added to the domains declaration. For example, articles could also include a boat, a house, or a bankbook. For a boat, you can make do with a functor that has no arguments attached to it. On the other hand, you might want to give a bank balance as a figure within the bankbook. The domains declaration of articles is therefore extended to:

    articles     = book(title, author) ; horse(name) ;
   
                  boat ; bankbook(balance)
    title, author, name = symbol
    balance
    = real

Here is a full program that shows how compound objects from the domain articles can be used in facts that define the predicate owns.

/* Program ch05e04.pro */

Now compile and run the program with the following goal:

    owns(john, Thing).

Visual Prolog responds with:

    Thing=book("A friend of the family","Irwin Shaw")
    Thing=horse("blacky")
    Thing=boat
    Thing=bankbook(1000)
    4 Solutions

(1) Writing Domain Declarations: a Summary

This is a generic representation of how to write domain declarations for compound objects:

    domain =alternative1(D, D, ...);
       
         alternative2(D, D, ...);
                ...

Here, alternative1 and alternative2 are arbitrary (but different) functors. The notation (D, D, ...) represents a list of domain names that are either declared elsewhere or are one of the standard domain types (such as symbol, integer, real, etc).

Note:

The alternatives are separated by semicolons.

Every alternative consists of a functor and, possibly, a list of domains for the corresponding arguments.

If the functor has no arguments, you can write it as alternativeN or alternativeN( ) in your programs. In this book, we use the former syntax.

(2) Multi-Level Compound Objects

Visual Prolog allows you to construct compound objects on several levels. For example, in

    book("The Ugly Duckling", "Andersen")

instead of using the author's last name, you could use a new structure that describes the author in more detail, including both the author's first and last names. By calling the functor for the resulting new compound object author, you can change the description of the book to

    book("The Ugly Duckling", author("Hans Christian", "Andersen"))

In the old domain declaration

    book(title, author)

the second argument of the book functor is author. But the old declaration

    author = symbol

can only include a single name, so it's no longer sufficient. You must now specify that an author is also a compound object made up of the author`s first and last name. You do this with the domain statement:

    author = author(first_name, last_name)

which leads to the following declarations:

DOMAINS
    articles
            = book(title, author); ..   /* First level */
    author
              = author(first_name, last_name) /* Second level */
    title, first_name, last_name = symbol
           /* Third level */

When using compound objects on different levels in this way, it's often helpful to draw a "tree":

A domain declaration describes only one level of the tree at a time, and not the whole tree. For instance, a book can't be defined with the following domain declaration:

    book = book(title,author(first_name,last_name)) /* Not allowed */

An Example That Illustrates Sentence Structure

As another example, consider how to represent the grammatical structure of the sentence

    ellen owns the book.

using a compound object. The most simple sentence structure consists of a noun and a verb phrase:

    sentence = sentence(noun, verbphrase)

A noun is just a simple word:

    noun = noun(word)

A verb phrase consists of either a verb with a noun phrase or a single verb.

    verbphrase = verbphrase(verb, noun); verb(word)
    verb
    = verb(word)

Using these domain declarations (sentence, noun, verbphrase, and verb), the sentence ellen owns the book. becomes

    sentence(noun(ellen), verbphrase(verb(owns), noun(book)))

The corresponding tree is

A data structure like this might be the output of a parser, which is a program that determines the grammatical structure of a sentence. Parsing is not built into Visual Prolog, but we have included a parser implementing simple sentence analysis with your Visual Prolog package. (Try to run the project VPI¡¬PROGRAMS¡¬SEN_AN when you're ready to tackle this subject.)

Exercises

Write a suitable domains declaration using compound objects that could be used in a Visual Prolog catalog of musical shows. A typical entry in the catalog might be

    Show: West Side Story
    Lyrics: Stephen Sondheim
    Music: Leonard Bernstein

Using compound objects wherever possible, write a Visual Prolog program to keep a database of United States senators. Entries should include the senator's first and last name, affiliation (state and party), size of constituency, date of election, and voting record on ten bills. Or, if you're not familiar with United States senators, use any political (or other) organization that you're familiar with.

4. Compound Mixed-Domain Declarations

In this section, we discuss three different types of domain declarations you can add to your programs. These declarations allow you to use predicates that

take an argument, more than one type of more than one possible type

take a variable number of arguments, each of a specified type

take a variable number of arguments, some of which might be of more than one possible type

(1) Multiple-Type Arguments

To allow a Visual Prolog predicate to accept an argument that gives information of different types, you must add a functor declaration. In the following example, the your_age clause will accept an argument of type age, which can be a string, a real, or an integer.

DOMAINS
    age = i(integer); r(real); s(string)

PREDICATES
    your_age(age)

CLAUSES
    your_age(i(AGE)) :- write(Age).
    your_age(r(AGE)) :- write(Age).
    your_age(s(AGE)) :- write(Age).

Visual Prolog does not allow the following domain declaration:

DOMAINS
    age = integer; real; string
                  /* Not permitted. */

(2) Lists

Suppose you are keeping track of the different classes a professor might teach. You might produce the following code:

PREDICATES
    teacher(symbol, symbol, symbol)
/* First_name, Last_name, Class) */

CLAUSES
    teacher(ed, willis, english1).
    teacher(ed, willis, math1).
    teacher(ed, willis, history1).
    teacher(mary, maker, history2).
    teacher(mary, maker, math2).
    teacher(chris, grahm, geometry).

Here, you need to repeat the teacher's name for each class he or she teaches. For each class, you need to add another fact to the database. Although this is perfectly OK in this situation, you might find a school where there are hundreds of classes; this type of data structure would get a little tedious. Here, it would be helpful if you could create an argument to a predicate that could take on one or more values.

A list in Prolog does just that. In the following code, the argument class is declared to be of a list type. We show here how a list is represented in Prolog, but list-handling predicates are covered in chapter 7.

DOMAINS
    classes = symbol*
                     /* declare a list domain */

PREDICATES
    teacher(symbol, symbol, classes)
     /* (First, Last, Classes) */

CLAUSES
    teacher(ed, willis, [english1, math1, history1]).
    teacher(mary, maker, [history2, math2]).
    teacher(chris, grahm, [geometry]).

In this example, the code is more concise and easier to read than in the preceding one. Notice the domains declaration:

DOMAINS
    classes = symbol*

The asterisk (*) means that classes is a list of symbols. You can just as easily declare a list of integers:

DOMAINS
    integer_list = integer*

Once you declare a domain, it's easy to use it; just place it as an argument to a predicate declared in the predicates section. Here's an example of using an integer list:

DOMAINS
    integer_list = integer*

PREDICATES
    test_scores(symbol, symbol, integer_list)
/* (First, Last, Test Scores) */

CLAUSES
    test_scores(lisa, lavender, [86, 91, 75]).
    test_scores(libby, dazzner, [79, 75]).
    test_scores(jeff, zheutlin, []).

In the case of Jeff Zheutlin, notice that a list doesn't need to contain any elements at all.

Lists are discussed in greater detail in chapter 7.

Summary

These are the important points covered in this chapter:

A Visual Prolog program can contain many types of data objects: simple and compound, standard and user-defined. A simple data object is one of the following:

a variable; such as X, MyVariable, _another_variable, or a single underscore ( _ ) for an anonymous variable

a constant; a char, an integer or real number, or a symbol or string atom

Compound data objects allow you to treat several pieces of information as a single item. A compound data object consists of a name (known as a functor) and one or more arguments. You can define a domain with several alternative functors.

A functor in Visual Prolog is not the same thing as a function in other programming languages. A functor does not stand for some computation to be performed. It's just a name that identifies a kind of compound data object and holds its arguments together.

Compound objects can be regarded and treated as single objects; you use the functor to distinguish between different objects. Visual Prolog allows you to construct compound objects on several levels; the arguments of a compound data object can also be compound objects. With compound mixed domain declarations, you can use predicates that:

take an argument of more than one possible type (functor declaration).

take a variable number of arguments, each of a specified type (list declaration).

take a variable number of arguments, some of which might be of more than one possible type.