Home

 

Quick Start with Elisa  

Using Development System Demo's

 

Language Description

1. Lexical Elements
2. Basic Data Types and Expressions
3. Definitions
4. Streams
5. Backtracking
6. Statements and Special Expressions
7. Arrays

8. Lists
9. Descriptors
10. Components
11. Collections
12. Generic Components
13. Terms
14. Categories
15. Types 

16. Built-in Definitions
17. Higher-order Definitions

18. External Interfaces

Index

Data Structures

1. Sequences
2. Examples involving Lists
3. Trees
4. Graphs
5. Searching State Spaces
6. Language Processing
7. Knowledge Representations          

 

Tutorials

1. Multi-value Functions

2. Lists and Streams

3. Descriptors

4. Trees

 

Back Home Up Next

13 TERMS
13.1 What are Terms?
13.2 Representations of Terms
13.3 Creation of Terms
13.4 Pattern Matching
  13.4.1 Pattern Variables
  13.4.2 Pattern Constants
  13.4.3 Nested Patterns
  13.4.4 Patterns of Prefix and Infix Operations
  13.4.5 Patterns of Special Operations
  13.4.6 Relations between Patterns
  13.4.7 Patterns for Type Matching
13.5 Climbing Term Trees
  13.5.1 Example: Symbolic Differentiation
  13.5.2 Example: Symbolic Simplification
13.6 Symbols
13.7 Inspecting Compound Terms
13.8 Summary

 

In the past the majority of programming languages were based on the initial idea of what the primary task of a computer is: numerical computations. However, some languages such as Lisp and Prolog showed that the computer also could be used for symbolic computations. Symbolic computations are used to manipulate symbolic information in a systematic way.

Symbolic information is playing an important role in several area's. Algebraic formulas, logic relations, and natural language phrases are all examples of symbolic expressions which can be processed.

In Elisa symbolic expressions are represented by terms. In this chapter we will study the concept of terms.

We start with the question what terms are and how they are represented in the computer. The following sections are related to the composition and the decomposition of terms. Some examples are given of the manipulation of symbolic expressions.

 

13.1 What are Terms?

Let us assume that we want to write a program to manipulate algebraic expressions. One of the first things we need to know is how algebraic expressions are represented in the computer. In general, symbolic expressions are represented in Elisa as terms. Terms are the basic building blocks of symbolic expressions. Take the following example:

2 * x + a * f( -b, c, d * e)

In this example, 2, x, a, b, c, d, and e are terms, as well as 2 * x, -b, d * e, f( -b, c, d * e), and a * f( -b, c, d * e). The total expression is also a term.

More generally, a term may be one of the following elements:

* a value, such as 2, 4.5, 'a'

* a symbol, such as a, b, x, y

* a prefix operation term, such as -2, -b

* an infix operation term, such as a + b, 2 * x

* a function term, such as f( -b, c, d * e)

Terms representing values or symbols are called simple terms. Prefix operation terms, infix operations terms, and function terms are called compound terms. Simple terms do not contain other terms, compound terms are comprised of other terms.

A compound term consists of a functor and a number of arguments. For example, the compound term f( -b, c, d * e) consists of the functor f, and the arguments -b, c, and d * e. The arguments are again terms. The number of arguments of a compound term is called its arity. In our example, f(-b, c, d * e) has the arity of 3.

Terms of prefix and infix operations are also compound terms. The functor of an operation term is the operation symbol. For example, the functor of -b is the - symbol, the functor of 2 * x is the * symbol. The relationship with function terms may be more clear if we write 2 * x in a functional notation:

* ( 2, x)

Mark that this is not a legal Elisa expression!

The arity of a prefix operation term is always 1. An infix operation term has the arity of 2. The arity of a simple term is always zero.

As we saw, the notation for expressing terms is very general. Therefore, terms are not limited to algebraic expressions, but they can also be used to describe many other things. Some examples are:

  • Formulas: a * b - c, H2O + CO2 + Na
  • Relationships: likes (Mary, Bach), speaks (Peter, Spanish)
  • Properties: male(Peter), female(Susan), human(Socrates).
  • Structures: date (13, June, 1993), name ("John","Smith").
  • Sentences: loves (man (John), woman (Kate)).

In the following some typical examples will be worked out.

 

13.2 Representations of Terms

Terms can be represented as trees. Trees are often drawn upside down; we will follow that convention. The example:

2 * x + a * f(-b,c,d * e)

may be represented as a tree (see Figure 13-1).

|
+
/ \
/   \
*     *
/ \    / \
2   x   a   f
            / | \
            -  c  *
              |     / \
               b    d   e

Figure 13-1: A tree of terms

Trees are made of sub-trees. Trees and sub-trees are representing terms. A (sub-)tree consists of nodes and branches. Nodes are representing the functors of terms. Branches, representing arguments, are pointing to other nodes. The arity of a term determines the number of branches originating from that node. The initial node of a tree is called the root node.

A leaf node is a node without branches. A term represented by a leaf node is a simple term. Examples are: 2, x, a, b, c, d, f. All other nodes are representing compound terms.

As another example how trees can be used to represent terms we use the expression:

loves (man (John), woman (Kate))

which can be represented as a tree (see Figure 13-2).

|
loves
/   \
man   woman
|       |
John    Kate

Figure 13-2: A tree representation of terms

Exercises:

  • 13.1 Represent the following symbolic expression as a tree:
father (uncle (John), mother (father (Peter)))
  • 13.2 Represent the following symbolic expression as a tree:
parallel (road1, series (parallel (road2, road3), road4))

A tree is a perfect means to visualize the relationships between the different terms in a symbolic expression.

Terms are represented in the computer as data structures. There are specific data structures to represent integer values, real values, character values, boolean values, prefix operations, infix operations, and function calls. All these data structures are belonging to a collective data type term which is a predefined type in the language.

 

13.3 Creation of Terms

Terms can be created by using special language constructs in a program, as will be described in this section.

Let us start with a simple example of how terms can be created in a program. Suppose we want to use relationships such as "John likes Mary" and "Peter likes Ann". However, before we are able to do that, we first have to define the meaning of the likes relationship. That can be done in the following way:

likes (Person, Person) => term;

This so-called term-signature has the same format as the signature of a definition, with one exception, the => symbol. The => symbol, pronounced as 'is a', indicates that there are no definition rules associated with this signature and that a 'call' of this signature will result in the creation of a corresponding term.

If we assume that John and Mary are already known as names of Persons, we may now ask the system to evaluate the following expression:

likes (John, Mary)?

The system will assume that this is a call of a function definition. It tries to find the corresponding definition. However, it discovers that the corresponding signature indicates that there are no definition rules defined to transform the input values into output values. Therefore the system will make a compound function term representing the call:

likes (John, Mary)

The returned term is equivalent to the following tree (see Figure 13-3).

|
likes
/   \
John   Mary

Figure 13-3: A tree representation of likes (John, Mary).

The likes relation has an arity of 2. We can also define compound terms with other arities, such as:

mother (Person) => Person;
father (Person) => Person;

Based on these term-signatures we may now ask the system to evaluate compound expressions such as:

likes (father (Ann), mother (Peter))?

Because all corresponding signatures are term-signatures the system will create the analogous terms:

likes (father (Ann), mother (Peter))

The resulting term may be interpreted as "the father of Ann likes the mother of Peter". It is equivalent to the following tree (see Figure 13-4).

|
likes
/   \
father  mother
|       |
Ann    Peter

Figure 13-4: A tree representation of likes (father (Ann), mother (Peter))

In the preceding examples, we assumed that John, Mary, Peter, and Ann were names of persons which could be used as terms. We may specify that in the following way:

type Person = term;
     John   => Person;
     Mary   => Person;
     Peter  => Person;
     Ann    => Person; 

Because of the frequent occurrences of these kinds of term-signatures there is a short-hand notation for term names as illustrated by the following example:

John, Mary, Peter, Ann => Person;

With the preceding elements we are now able to show a complete session:

type Person = term;
John, Mary, Peter, Ann => Person;
mother (Person) => Person;
father (Person) => Person;
likes (Person, Person) => term;
likes (father (Ann), mother (Peter))?

As we saw, the system will return with the compound term:

likes (father (Ann), mother (Peter))

In the previous examples we used the predefined term type or equivalent types for arguments and parameters. However, that is not always adequate. Sometimes we want to use different types as in the following example:

Date (Day = integer, Month = text, Year = integer) => term;

This term-signature can be used to record dates in the following way:

Date (19, "May", 1995)?

The system will return with the following compound term:

Date (19, "May", 1995)

This result can be represented by the following tree (see Figure 13-5).

|
Date
/  |  \
/   |   \
20 "May" 1995

Figure 13-5: A tree representation of a Date

The arguments of a compound term, such as 20, "May", 1995, are also represented as terms. As a general rule, argument values of a term-signature call are represented by corresponding terms.

Term-signatures can also be used to specify terms which are representing prefix operations, infix operations, and functions. Examples are:

+ term => term;
- term => term;
term + term    => term;
integer + term => term;
term * term    => term;
integer * term => term;
f (term, term, term) => term;

If we assume that:

a, b, c, d, e, x => term;

then the evaluation of the following expression:

2 * x + a * f ( -b, c, d * e)

will create a tree of terms as represented by Figure 13-1.

There are a number of rules associated with term-signatures:

1. The parameters of a term-signature may be of any type - including the term type itself.

2. Because a term-signature always represents a single term, its result specification can only specify single term types. Other types and other quantifiers are not allowed.

3. The selection of term-signatures is the same as used for definitions: the function-name or operation-symbol, the argument types, and the number of arguments are used to select the proper definition or term-signature.

4. The evaluation of a function or operation call which refers to a term-signature, may result in the creation of a number of terms representing the arguments before a compound term representing the call is created. See, for example, Figure 13-1 and Figure 13-4.

5. Arguments are evaluated before they are used in a compound term. For example:

Date ( 3 * 9 - 7, "May", 1950 + 45)?

will deliver the same compound term as represented by Figure 13-5.

Exercises:

  • 13.3 Make a program which creates a tree of terms representing:
father (uncle (John), mother (father (Peter)))

Compare the result with the tree of Exercise 13.1.

  • 13.4 Make a program which creates a tree of terms representing:
parallel (road1, series (parallel (road2, road3), road4))

Compare the result with the tree of Exercise 13.2.

There is also another, more explicit way to create a term. If we want to convert a value of a given type into a term type we can use the term type, or its equivalent, in a so-called type conversion expression:

term: expression

For example, term:15 converts the integer value 15 into the term value 15.

 

13.4 Pattern Matching

In the preceding section we discussed how terms are composed. In this section we will study how terms can be decomposed. The decomposition of terms is based on pattern matching.

Pattern matching is used to select specific terms from a set of terms and to retrieve the arguments from those selected terms. It makes use of patterns which are characterizing the terms to be selected.

Let us give a simple example. In the previous section we assumed that John likes Mary and Peter likes Ann and we discussed how to create terms of such relations. We may expect that there are many more of those like-relations. To find out who likes who, we may write a definition in the following way:

f (term) -> optional (list (term));
f (likes ( X, Y)) = { X, Y};

The input of the function f is a term. If the term is a likes term, the arguments of the term, called X and Y, are returned in a list. We call likes (X, Y) a pattern.

In Section 3.4, "Definition Rules", it has been explained that parameters may not only be names, but also literals, and named values. In this section we learn that a parameter may also be a pattern.

We will use the term parameter-expression to denote all the possibilities at a given parameter position. Thus, a parameter expression is one of the followings:

* a parameter name

* a literal

* an expression (preceded by a prefix = sign)

* a pattern

A pattern describes a number of possible terms. That means that patterns may only be used for term parameters, or for parameter types which are declared to be equivalent to the term type.

A pattern consists of a given functor and a list of pattern expressions. A pattern-expression has the same syntax as a parameter expression. Consequently, a pattern expression may be a name, denoting a pattern variable, a literal, denoting a specific value, an expression value (preceded by a = sign), or another pattern.

The number of pattern expressions in a pattern specifies the arity of the pattern. For example, the pattern likes (X, Y) has the arity of 2. It describes all terms with the functor likes and the arity of 2.

 

13.4.1 Pattern Variables

Pattern variables are represented by simple names. For example, in the pattern likes (X, Y) are X and Y both pattern variables. In case of a successful match a pattern variable contains the value of the corresponding argument.

Suppose we have a list of men and women, such as:

People = { man(John), woman(Mary), woman(Ann), man(Peter) }; 

This is a list of terms. Let us now assume that we want to select all the names of the women in the list. We will use the following definition:

Female (term) -> optional (term);
Female (woman (X)) = X;

This definition has as input a term. There is only one definition rule given. It has one parameter-expression which is a pattern. In this example, woman(X) is the pattern. Its functor is woman, its arity is one, and the pattern expression is the pattern variable X.

If the functor of an input term is woman and its arity is one, then we are talking about a successful match. In that case the value of the argument will be assigned to the pattern variable X. The right hand side of the definition rule may now use the value of X. In this case, the value of X is simply returned to the caller.

If, in our example, the functor of an input term is man (or any other functor unlike woman) then there is no successful match of the input term against the pattern. In that case the definition does not apply. So, no value can be returned. That is the reason we used the optional quantifier in the result specification. In general, if in a definition, not all input terms may be matched, the optional or multi quantifier should be specified.

Our example definition can now be used in the following way:

Female (items (People))?

The system returns with the stream:

Mary
Ann

 

13.4.2 Pattern Constants

Pattern constants are literals or named values. Named values are prefixed by a = sign. A pattern constant specifies that the corresponding argument must have the same value for a successful match.

The following example illustrates the use of pattern constants. Suppose we have list of mothers, as in:

Mothers = { mother (Ann, Mary),
            mother (Kate, Alice), 
            mother (Ann, Susan),
            mother (Ann, John) };

In this list of terms is Ann the mother of Mary, and so on.

We would like to know all the sons and daughters of Ann. We will use the following definition:

Children (term) -> optional (term);
Children (mother (= Ann, X)) = X;

The pattern in this definition, mother (=Ann, X), has two pattern expressions. The first is a constant, the second a variable. The constant specifies that for a successful match the first argument of the input term must be the symbol Ann. If that is the case then the second argument of that term will be assigned to the variable X. All other actions are similar to the actions of the previous definition of Female.

This definition can now be used in the following way:

Children (items (Mothers) )?

The system will return the stream:

Mary
Susan
John

However, the problem with this version of Children is that it is restricted to the sons and daughters of Ann. We may generalize Children in the following way:

Children (Mother = term, Relation = term) -> optional (term);
Children (X, mother( X, Y)) = Y;

This definition has two input terms, one for the name of the mother and the other for the mother relationship. The pattern used for the second parameter has a pattern variable which name is the same as used for the first parameter. This means that the two values must be equal. In general, equal names of variables in parameter expressions require equal argument values for a successful match.

This definition can now be used in the following way:

Children (Ann, items (Mothers))?

The system will return the same set of answers as with the previous questions:

Mary
Susan
John

 

13.4.3 Nested Patterns

Patterns may contain other patterns. A pattern-expression in a pattern may be another pattern, which means that for a successful match the corresponding argument must be in accordance with this nested pattern.

Suppose we have list of mothers, together with their sons and daughters as indicated:

mothers = { mother (Ann, female (Mary) ),
            mother (Kate, female (Alice) ), 
            mother (Ann, female (Susan) ),
            mother (Ann, male (John) ) };

This is a list of terms where Ann is the mother of a female person called Mary, and so on. We now like to know all the daughters of a mother. We will use the following definition:

Daughter (Mother = term, Relation = term) -> optional (term);
Daughter (X, mother (X, female (Y)) ) = Y;

The pattern mother (X, female (Y)) is a nested pattern.

This definition can now be used in the following way:

Daughter (Ann, items (mothers))?

The system will return the stream:

Mary
Susan

 

13.4.4 Patterns of Prefix and Infix Operations

Patterns of prefix and infix operations can also be used to match corresponding terms. Suppose we have the following list of expressions:

Expr = { a + b, + c - d, e * f + g, 2 * k + 5 * q };

We want to select only the infix + operations to form corresponding plus terms. We use the following definitions:

sum (term, term) => term;
plus (term) -> optional (term);
plus (X + Y ) = sum (X, Y); 

The pattern in the plus definition is X + Y.

This definition can now be used in the following way:

plus (items (Expr))?

The system will return the stream:

sum (a, b)
sum (e * f, g)
sum (2 * k, 5 * q)

Exercise:

  • 13.5 Make a definition which transforms X + X into 2 * X.

 

13.4.5 Patterns of Special Operations

Patterns of operations used in special operations can also be used to match corresponding terms. Suppose we have the following list of conditionals:

Cond = { if a then b * c, if d < e then p else q , f + g when r < s };

Let us assume that we want to form a list of the expressions in a conditional. Because conditionals are not unary or binary operations we will use a built-in function, called phrase, to indicate that its argument should be treated as a term which can be used for pattern matching. We illustrate its use in the following definitions:

Conditional (term) -> optional (list (term));
Conditional ( phrase(if X then Y) )         = { X, Y }; 
Conditional ( phrase(if X then Y else Z) )  = { X, Y, Z };
Conditional ( phrase(X when Y) )            = { Y, X }; 

This definition can now be used in the following way:

Conditional (items (Cond))?

The system will return the stream:

{ a, b * c }
{ d < e, p, q }
{ r < s, f + g }

 

13.4.6 Relations between Patterns

Relations between patterns are used to describe certain combinations of arguments. Suppose we want to describe relations between persons. Let us assume that we like to formulate the biblical father relations as described in Genesis 4:

type Person = term;
type relation = term;
Adam, Abel, Cain, Enoch, Irad => Person;
father (Person, Person) => relation;
FatherList = { father (Adam, Abel),
               father (Adam, Cain),
               father (Cain, Enoch),
               father (Enoch, Irad) };

At the first two lines the notion of Person and relation are introduced. The persons involved are given at the third line. The fourth line gives a signature of the father relation and the actual relations are collected in the FatherList as terms. In this example, we use the convention that father (Adam, Abel) means Adam is-the-father-of Abel.

Let us assume that we want to obtain only the names of the sons of Adam. We will therefore introduce the following definition:

AdamSons (relation) -> optional (Person);
AdamSons (father (=Adam, X)) = X;

This definition will test if the first term of a father relation is the name of Adam; if so, the second term, being the name of the son will be returned. After the query:

AdamSons (items (FatherList))?

the following answers will be returned:

Abel
Cain

The problem with this solution is that it is restricted to the sons of Adam. It is the same problem as we discussed with the daughters of Ann. We would prefer a more general solution where we could ask types of questions like: Give me the names of all the sons of a given father. We will split that question into two parts. The first part is: Give me for a given person and a given relation the name of the son if the person and the father in the relation matches. The second part is: Give me for a given person all the names of the sons in the FatherList:

son (Person, relation) -> optional (Person);
son (P, father (P, X)) = X;
sons (Person) -> multi (Person);
sons (P) = son (P, items (FatherList));

If we are now asking for the sons of Adam with:

sons (Adam)?

we will also obtain:

Abel
Cain

However, with these definitions we can also ask for the sons of Abel, Cain, and so on.

Suppose we want to ask questions about more complicated relationships, such as: Who was the grandfather of Enoch? To answer that type of questions we have to define what a grandfather is and what the relationship between a father and a grandfather is. Therefore we will define for relations a binary and operation which can be applied to any pair of relations:

grandfather (Person, Person) => relation;
relation & relation -> optional (relation);
father (X, Y) & father (Y, Z) = grandfather (X, Z);

The definition rule at the last line states: if X is the father of Y and Y is the father of Z then X is the grandfather of Z. Note, that the binary operation & we used earlier for boolean values can also be used for other types, provided its meaning is defined, as is done here.

Now we are able to generate a list of grandfathers, based on our FatherList:

GrandfatherList = { items (FatherList) & items (FatherList) };

In this definition each father relation in the FatherList is compared with all other father relations to see if the grandfather relation holds. If we now ask for all the items of the GrandfatherList with:

items (GrandfatherList)?

the following answers are given:

grandfather (Adam, Enoch)
grandfather (Cain, Irad)

 

13.4.7 Patterns for Type Matching

Because simple terms may represent values of different types, such as integer values, real values, and so on, it is sometimes necessary to match for arguments of certain types. Such a type match has the following form:

type-expression : pattern-variable

The type-expression specifies the required type of the corresponding argument. With a successful match the argument will be assigned to the pattern variable. The pattern variable will be of the type specified by the type-expression.

To illustrate type matching we assume that we have the following list of term values:

Values = { term:23, 3.4, 45, -23, ... };

Suppose we want to quadruple all the values in the list. We will use the following definition:

quadruple (term)      -> optional (term);
quadruple (integer: I) = term: (4 * I);
quadruple (real: R)    = term: (4.0 * R);

This definition has two definition rules. The first rule tests if the term represents an integer value. If that is the case the integer value will be multiplied by 4. The resulting value will be converted to a term and returned. The second rule does the same for real values.

Another example of type matching is demonstrated in the following example. Suppose we have the following list of terms which may be interpreted as 'instructions':

Instructions = { square (12),
                 double (23.5), 
                 triple (34), 
                 square (3.4), ...};

We may now define the interpretation of these instructions in the following way:

Interpret (term) -> optional (real);
Interpret (double (integer: I)) = real (I + I);
Interpret (triple (integer: I)) = real (3 * I);
Interpret (square (integer: I)) = real (I * I);
Interpret (double (real: R)) = R + R;
Interpret (triple (real: R)) = 3.0 * R;
Interpret (square (real: R)) = R * R;

In contrast to the previous example, all the results will be real numbers instead of terms. We may now use our simple interpreter in the following way:

Interpret (items (Instructions))?

The system will respond with the stream:

144.0
47.0
102.0
11.56

 

13.5 Climbing Term Trees

Although, until now, the terms used in our examples are very simple, in practical situations terms can be quite complex. Because a lot of symbolic information can be stored in term trees there is often a need to analyze and decompose the terms in a tree and to manipulate the corresponding sub-trees in an easy way.

There are two features in the language which are very helpful in this way: that are pattern matching and recursion.

Recursion is a natural tool for dealing with tree structures, because we can often reduce operations on trees to operations on their sub-trees, which reduce in turn to operations on the sub-trees of the sub-trees, and so on, until we reach the leaves of the tree. So, recursion gives us the possibility of climbing trees up and down in a natural way.

Pattern matching combined with recursion provides us with a powerful combination for defining operations on term trees.

To illustrate operations on term trees we will use two examples: one based on symbolic differentiation of algebraic formulas, the other designated for the simplification of those formulas.

Before we are going to discuss that in detail we will first introduce algebraic formulas as examples of symbolic expressions. As we saw earlier symbolic expressions are based on terms. Therefore we are defining a component for Formulas ( see Figure 13-6).

component Formulas;
 type formula = term;
              + formula => formula; 
              - formula => formula;
      formula + formula => formula;
      formula + integer => formula;
      integer + formula => formula;
      formula - formula => formula;
      formula - integer => formula;
      integer - formula => formula;
      formula * formula => formula;
      formula * integer => formula;
      integer * formula => formula;
      formula / formula => formula;
      formula / integer => formula;
      integer / formula => formula;
      formula ** formula => formula;
      formula ** integer => formula;
      integer ** formula => formula;
      abs (formula)      => formula;
      sqrt (formula)     => formula;
      sin (formula)      => formula;
      cos (formula)      => formula;
      ln (formula)       => formula;
      exp (formula)      => formula;
begin
     << empty implementation section >>
end component Formulas;

Figure 13-6: A component for algebraic formulas

The component introduces in the interface-section the type formula, and the term-signatures of the usual infix and prefix operations and some functions. The implementation-section of the component is empty. All operations used in formulas are represented by corresponding terms.We are now able to use formulas in our applications. Let us use the following example:

a, b, c, x => formula;
y = a * x ** 2 + b * x + c;

The execution of the definition of y results in the symbolic expression a * x ** 2 + b * x + c, which is assigned to y. The expression is evaluated according to the operator precedence rules as ((a * (x ** 2)) + (b * x)) + c and each sub-expression is represented by a term. The whole expression is represented by a tree of terms which has the following form (see Figure 13-7).

                                                       x   2
                                                        \ / 
                                                     a  **  b  x
                                                      \ /    \/
                                                       *     *
                                                        \   /
                                                         \ /
                                                          +     c
                                                           \   /
                                                            \ /
                                                             +
                                                             |

Figure 13-7: Tree of formula a * x ** 2 + b * x + c

Notice that we have not used the convention of drawing trees upside down. A more natural view may help to visualize tree climbing as discussed in the following.

 

13.5.1 Example: Symbolic Differentiation

In mathematics, symbolic differentiation is a procedure that converts an algebraic expression into another algebraic expression which is called the derivative. This procedure can be written in Elisa as a definition which has as input an algebraic expression and a variable, and as output the derivative of the expression with respect to the variable. The definition makes heavily use of pattern matching and recursion. Figure 13-8 shows a component which is dedicated to symbolic differentiation.

component Differentiation;
   diff (formula, formula) -> formula;
begin
   diff (x, x)              = formula: 1;
   diff (u + v, x)          = diff (u, x) + diff (v, x);
   diff (u - v, x)          = diff (u, x) - diff (v, x);
   diff (u * v, x)          = diff (u, x) * v + u * diff (v, x);
   diff (u / v, x)          = diff (u, x) / v - u * diff (v, x) / v ** 2;
   diff (y ** integer:n, x) = n * y ** ( n - 1) * diff (y, x);
   diff (sin (y), x)        = cos (y) * diff (y, x);
   diff (cos (y), x)        = -sin (y) * diff (y, x);
   diff (ln (y), x)         = 1 / y * diff (y, x);
   diff (others, x)         = formula: 0;
end component Differentiation;

Figure 13-8: Symbolic Differentiation Component

Notice that there is only one signature for the differentiation operation but that there are many definition rules: for each possible operation or function symbol in the formula a rule has been given. So each rule represents a particular case. If the differentiation operation is applied to a formula, then the patterns in the parameter-expressions of the first rule will be compared with the input formulas. If they match then the expression of the right-hand side of the rule will be evaluated. If they do not match, the patterns of the next rule will be taken for comparison. This process will continue until the patterns of a rule are matching or until the last rule of the definition has been met. In our example, the last rule represents the most general case. It will always be applied if no preceding ones do.

Suppose we want to differentiate y as given by the previous example. Then we start with the following situation:

diff (y, x)

Substitution of the value of y gives us:

diff (a * x ** 2 + b * x + c, x)

Now we start with tree climbing! We always start at the root node. Because the most right + operator is the root node at the bottom of the tree it will be used first for matching. We see that the second definition rule is matching with the + operator and we may now apply the right-hand part of the second definition rule which results into:

diff (a * x ** 2 + b * x, x) + diff (c, x)

By proceeding from left to right the left sub-expression can be further evaluated by applying again the second definition rule:

( diff (a * x ** 2, x) + diff (b * x, x) ) + diff (c, x)

So, we have climbed the tree of Figure 13-7 to the second + node.

The most left sub-expression can be further evaluated by applying the fourth definition rule dedicated to u * v patterns.

( (diff (a, x) * (x ** 2) + a * diff (x ** 2, x)) + diff (b * x, x)) + diff (c, x)

We have now climbed to the most left * node.

The most left sub-expression is further evaluated. Because there is no specific rule for a only the last and most general rule applies:

(((formula: 0) * (x ** 2) + a * diff (x ** 2, x)) + diff (b * x, x)) + diff (c, x)

We have reached the left-most (sub-) top! It took us a number of recursions before we reached leaf a.

Now we are climbing down. We are reaching the most left * node again and try to perform the multiplication. Because both operands are known and both are of the formula type, this is an irreducible sub-expression which will result in the term 0 * (x ** 2) and this is the first result of our differentiation exercise. The following steps are similar to the steps we discussed so far. It involves further tree climbing up and down.

Exercise:

  • 13.6 Complete this differentiation process till the final answer has been found.

The final result of this exercise is:

0 * x ** 2 + a * (2 * x ** 1 * 1) + (0 * x + b * 1) + 0

As you see, all unnecessary parentheses have been removed. The remaining parentheses are reflecting the structure of the resulting tree.

As a final remark: The differentiation definition may be considered as a description of a procedure to derive from a symbolic expression another symbolic expression. But equally, it may also be seen as a procedure which transform a tree into another tree.

Exercises:

13.7 Draw the tree corresponding to the result of the differentiation exercise. Are there common parts of the input and the output trees?

13.8 Are the symbolic expressions (X * Y) * Z, X * (Y * Z), and X * Y * Z all equal? Hint: Draw the corresponding trees.

13.9 Write a definition which determines if two symbolic expressions are equal. Assume that these symbolic expressions only contains the infix operators +, -, *, and /.

 

13.5.2 Example: Symbolic Simplification

The result of the differentiation exercise might not be according to your expectations. You probably would have expected a much simpler formula as a result. And that is because of the simplifications we are making if we do the exercise ourselves. To bring our result in a simplified form we could have extended our differentiation definition with simplification features. But, because simplification is also required in many other situations we have separated simplification in a dedicated component as defined in Figure 13-9.

component Simplification;
   simplify (formula) -> formula;
begin
   simplify (x + y) = reduce ( simplify (x) + simplify (y) );
   simplify (x - y) = reduce ( simplify (x) - simplify (y) );
   simplify (x * y) = reduce ( simplify (x) * simplify (y) );
   simplify (x / y) = reduce ( simplify (x) / simplify (y) );
   simplify (x ** y)= reduce ( simplify (x) ** simplify (y) );
   simplify (x)     = reduce (x);
   reduce (formula) -> formula;
   reduce ( 0 + x) = simplify (x);
   reduce ( x + 0) = simplify (x);
   reduce ( integer: x + integer: y) = formula: (x + y);
   reduce ( x - 0) = simplify (x);
   reduce ( 0 - x) = simplify (- x); 
   reduce ( integer: x - integer: y) = formula: (x - y);
   reduce ( 0 * x) = formula: 0;
   reduce ( x * 0) = formula: 0;
   reduce ( 1 * x) = simplify (x);
   reduce ( x * 1) = simplify (x);
   reduce ( integer: x * integer: y) = formula: (x * y);
   reduce ( x / 1) = simplify (x);
   reduce ( 0 / x) = formula: 0;
   reduce ( integer: x / integer: y) = formula: (x / y);
   reduce ( x ** 1) = simplify (x);
   reduce ( x ** 0) = formula: 1;
   reduce ( integer: x ** integer: y) = formula: (x ** y);
   reduce (x) = x;
end component Simplification;

Figure 13-9: Algebraic Simplification Component

The simplification procedure consists of two mutual recursive definitions, both based on pattern matching. This is because the simplification procedure creates many intermediate sub-trees which may be simplified further.

With this component it is possible to simplify algebraic expressions to some degree. This component will eliminate all redundant terms based on 0 and 1 and will lead to considerable size reduction. For example, simplification of the result of our previous differentiation example:

simplify (0 * x ** 2 + a * (2 * x ** 1 * 1) + (0 * x + b * 1) + 0)

will result in:

a * (2 * x) + b

which is considerably shorter.

Exercise:

13.10 Use tree climbing to verify the result.

13.11 Modify the simplification procedure such that a * (2 * x) is rewritten as 2 * a * x.

13.12 Modify the simplification procedure such that a + a is rewritten as 2 * a.

In the preceding examples we discussed how pattern matching and recursion can be used to construct quite complex procedures for symbol manipulations. We used tree climbing as an aid in visualizing the operations.

 

13.6 Symbols

In previous sections we introduced symbols as symbolic representations of names or identifiers. Because symbols are often used in symbolic expressions there is sometimes a need to compare symbols. For example, it is customary to write in an algebraic expression b * a as a * b. If we want to achieve this by means of a simplification procedure we will need to compare the textual representations of symbols.

As a consequence, the following operations are defined on symbols which are all belonging to the predefined type symbol:

type symbol;
     symbol == symbol -> boolean;
     symbol <> symbol -> boolean;
     symbol <  symbol -> boolean;
     symbol <= symbol -> boolean;
     symbol >  symbol -> boolean;
     symbol >= symbol -> boolean;
     text (symbol)    -> text;
     symbol (text)    -> symbol;

The binary operations ==, <>, <, <=, >, >= are defined for symbol operands. They are used for comparison and they represent the familiar operations of equal to, not equal to, less than, less than or equal to, greater than, and greater than or equal to. The result is a boolean value. Comparing two symbol strings works in the same way as defined for text strings. For example, the result of the expression a < b where a and b are both symbols, will be true.

In addition to the comparison operations there are two other operations related to text conversion.

One is a conversion operation which converts a symbol into text. For example, evaluation of the expression text(a) will result in the text string "a".

The other operation does the opposite: it converts a text string into a symbol. For example, symbol("X") will result in the symbol X.

Symbolic names of type symbol may be introduced by name-signatures. For example:

H2O, CO2, H2SO4, HCl => symbol;

may be used as symbols of chemical components.

Symbolic names of any type are considered to be symbols which may be used in patterns of parameter expressions. For example, if we want to rearrange an algebraic formula in such a way that Y + X will become X + Y, we may do that in the following way:

use Formulas;
X, Y, Z => formula;
symbol + symbol => term;
rearrange (term) -> term;
rearrange ( symbol: u + symbol: v) = if u < v then u + v else v + u;
rearrange ( others) = others;

We may now use this in the following dialogue:

rearrange (X + Y)?
X + Y
rearrange (Y + X)?
X + Y
rearrange (Y - X)?
Y - X

 

13.7 Inspecting Compound Terms

There are three built-in definitions for inspecting compound terms and one definition for setting an argument. The signatures of these definitions are:

functor (term) -> symbol; 
arity (term) -> integer;
argument (term, integer) -> term;
set_argument (term, integer, term) -> nothing;

The functor function returns the functor of the input term. The input term must be a compound term. For example, functor (woman(Mary)) will be the symbol woman.

The arity function returns the arity of the input term. If the term is a simple term the arity is zero, otherwise it is the number of arguments of the term. Therefore, the arity function can be used to test if a term is a simple term or a compound term.

The argument (T, I) function returns the I th argument of the input term T. The value of I must be positive and may not exceed the arity of the term T. For example,

argument (loves (man (John), woman (Kate) ), 2 )

will return woman (Kate).

The set_argument (T, I, U) function assigns U to the I th argument of the input term T. The value of I must be positive and may not exceed the arity of the term T. For example,

set_argument (loves (man (John), woman (Kate)), 2, woman (Alice))

will change the first input term into:

loves (man (John), woman (Alice))

We will give another example of how these functions can be used. Suppose we want to design a procedure which will return all the leaves of a term tree, without knowing the structure and contents of the tree. This will be done by the following recursive definition:

Leaves (Tree = term) -> multi (term);
Leaves (Tree) = if arity (Tree) == 0 then Tree << is Leaf >>
                                     else Leaves (argument (Tree, 1 .. arity(Tree) ) ); 

This definition is a recursive procedure which returns a stream of terms, being the leaves of the input tree.

The definition rule starts with testing if the arity of the input term is zero. If that is true the input term is a simple term which is returned. Otherwise, the Leaves procedure is recursively called for each argument of the input term.

 

13.8 Summary

  • Terms are used to represent symbolic information. A term is a value, a symbol, a prefix operation term, an infix operation term, or a function term. Terms representing values or symbols are called simple terms. Prefix operation terms, infix operations terms, and function terms are called compound terms.
  • Terms may be represented as trees. A (sub-)tree consists of nodes and branches. Nodes are representing the functors of terms. Branches, representing arguments, are pointing to other nodes. The arity of a term determines the number of branches originating from that node. A leaf node is a node without branches.
  • Pattern matching is used to select specific terms from a set of terms. It makes use of patterns which are characterizing the terms to be selected. Patterns are part of parameter expressions. A parameter expression is one of the followings: a parameter name, a literal, an expression (preceded by a prefix = sign), or a pattern. A pattern may be a name, denoting a pattern variable, a literal, denoting a specific value, a named value, or another pattern. Patterns may be nested: they may contain other patterns.
  • A successful match between an argument and a corresponding parameter-expression depends on the current value of the argument and on the structure of the parameter-expression:
  1. If the parameter-expression is a literal the corresponding argument should represent the same value.
  2. If the parameter-expression is a single name which has not occurred already in a parameter-expression of the same definition rule then any value of the argument will match.
  3. If the parameter-expression is a single name which has already occurred in a parameter-expression of the same definition rule then the current entity of the argument should be equal to the entity of the argument of the previous parameter.
  4. If the parameter-expression is an expression preceded by an unary = sign then that expression will be evaluated and the resulting entity - which must be a single entity - should be equal to the entity of the argument. Note: Because the = sign is used here as a unary operator which has the highest priority, parentheses should be used if operators with lower priorities are part of the expression.
  5. If the parameter-expression is a type-expression, followed by a colon, followed by a name then the corresponding argument should be of the type denoted by the type-expression.
  6. If the parameter-expression is an expression, representing a pattern, then the corresponding argument should represent a term. The match is successful if the term has the same pattern as the parameter-expression, according to the rules given above.

 

Top of Page   Part 1: Language Description   Chapter 13: Terms

 
            

 

 

  <!-- Start of StatCounter Code for Default Guide -->
<script type="text/javascript">
var sc_project=11338540;
var sc_invisible=0;
var sc_security="f2d7a14f";
var sc_https=1;
var scJsHost = (("https:" == document.location.protocol) ?
"https://secure." : "http://www.");
document.write("<sc"+"ript type='text/javascript' src='" + scJsHost+
"statcounter.com/counter/counter.js'></"+"script>");
</script>
<noscript><div class="statcounter"><a title="Web Analytics Made Easy -
StatCounter" href="http://statcounter.com/" target="_blank"><img
class="statcounter" src="//c.statcounter.com/11338540/0/f2d7a14f/0/"
alt="Web Analytics Made Easy - StatCounter"></a></div></noscript>
<!-- End of StatCounter Code for Default Guide -->