Copyright 1995-1997 Joseph Bergin. All rights reserved.

Chapter 3 Fundamental Ideas and the Class Hierarchy

Lovliest vision far Of all Olympus' faded hierarchy!
All the better; their fraction is more our wish than their faction: but it was a strong composure a fool could disunite.
Shakespeare, Trolius and Cressida

This was a merry message.
Shakespeare, Henry V

In this chapter and the two which follow, we shall lay the foundation for the development and use of a large software library of general purpose tools that may be used in virtually any development project to shorten development time and help guarantee correctness of the code. We shall describe a number of "Classes" of "objects" arranged into "compilation units" which may be IMPORTED by programs or other compilation units and classes. Later chapters describe the implementation of the classes. In Chapter 5 we will show two examples of the use of the classes: to create deterministic finite state automata simulators, and to show a simple translator from infix expressions to postfix.

3.1 Classes and Objects

In object oriented programming a running program is a collection of communicating objects which carry out the computation task collectively by requesting services from and providing services to other objects in the system. The objects are usually thought of as having a form of independent existence and a large degree of autonomy. An object is more than just a block of data like a record. One object, or a procedure or a program block (called a "client") is generally not permitted to modify another object. It is only permitted to request service from the object by sending it a message naming the service required. The object receiving the message interprets it as appropriate and then fulfills the service request if possible by executing a method using parameters provided by the client.

To solidify these ideas, here is the declaration of a Class called FiniteStack.T. From this point on we will be using a naming convention that is useful in practice. All class names will be defined in interfaces whose name is a capitalized noun descriptive of the main class exported by the interface (e.g. FiniteStack). The main class itself will always be called just T (for TYPE). When we are writing code outside of this interface (and its implementation) we shall refer to the class using the interface name followed by ".T" as in FiniteStack.T. All instance variables (field) names will begin with the initial letter f, followed by a noun descriptive of the concept modeled by the variable. all method names will begin with a lower case letter. This convention helps class names, methods, and instance variable names stand out in certain contexts. Methods which are functions have names which are nouns or short noun phrases which describe the returned result. Procedural methods have names which are verbs or verb phrases describing what the procedure does. One exception is for function that return a Boolean result. These are given names that are adjectives, describing the condition that they test. Note also that abbreviations are seldom used, that capitalization is consistent, and that when two words are catenated to form a compound word, the first letter of the second and subsequent word is capitalized as in FiniteStack. The following declaration is an improvement on the declarations at the end of the last chapter, but it is possible to improve it further. See the exercises for hints. The following declarations would be put into a file named FiniteStack.i3. If your operating systems is unable to use names longer than 8 characters, the name of the file may need to be truncated or otherwise modified. In any case, the filename is intended to name the principal type of the interface.


INTERFACE FiniteStack;
IMPORT  Object;

CONST FiniteStackMax = 20;

TYPE	
	T = Object.T OBJECT
			fTop: INTEGER:
			fElements: ARRAY [0.. FiniteStackMax-1] OF REFANY;
		METHODS
			initStack()		:= InitStack;
				(* Initialize a newly created stack.  Leaves it empty.*)
			push(o: REFANY)		:= Push;
				(* Insert d into the storage.  No op if the structure is full. *)
			pop()			:= Pop;
				(* Remove the most recently inserted object.  No Op if empty. *)
			top(): REFANY		:= Top;
				(* Return a reference to the last object inserted into the storage. *)
			empty(): BOOLEAN	:= Empty;
				(* True if there are no objects in storage. *)
			full(): BOOLEAN		:= Full;
				(* True if there is no room for additional objects to be stored. *)
		END;

	PROCEDURE InitStack(self: T): T ;
	PROCEDURE Push(self: T; o: REFANY) ;
	PROCEDURE Pop(self: T)
	PROCEDURE Top(self: T): REFANY ;
	PROCEDURE Empty(self: T): BOOLEAN ;
	PROCEDURE Full(self: T): BOOLEAN ;

	PROCEDURE New(): T;

(* Objects are inserted into the finite stack using push. They are removed 
and retrieved with pop and top respectively.  The behavior is last in-first 
out (LIFO), meaning that the most recently pushed object will be the next 
popped.  NIL will be returned if the client attempts to pop an empty finite 
stack, and attempting to push onto a full one results in no operation (no-op) 

FORMAL RULES
	1.  Initialization must be done once for any stack object. 
		This is done correctly by FiniteStack.New.
	2.  Immediately after initialization, empty returns true and full 
		returns false.  Pop is illegal and results in no-op, and top, while 
		also illegal, returns NIL.
	3.  Immediately after push, empty returns false and full may return true.  
	4.  If full would return true then push is illegal and results in a no-op.  
		However if push  fails to insert X by reason of executing when full is 
		true, the stack may not properly be LIFO.  
	6.  FiniteStack.New returns a new allocated and initialized (empty) stack.
	7.  At most some fixed finite number (FiniteStackMax ) elements 
		may be stored at any time.  If the stack currently holds 
		FiniteStackMax elements then full returns true.  
*)

END FiniteStack.

We illustrate the above declaration with the Class Photo, shown in Figure 3.1, which indicates that the methods are to be public, and the instance variables, enclosed within the boundary, are to be private.




                      

Having made the above declaration, we may want a finite stack object called ExpressionStack from class FiniteStack to be available when the program executes. Usually we will want to refer to this object indirectly using a pointer. Therefore the declaration would be



VAR	ExpressionStack: FiniteStack.T;

The variable ExpressionStack is called a reference variable because it is used to refer indirectly to an object within some class. Its actual value will not automatically be an object, however. This declaration states that ExpressionStack is to be an object, but a declaration alone is not quite enough to create the ExpressionStack. Objects are created dynamically at run-time, so in some code block in which the above declarations are visible we will need to execute:


	ExpressionStack := FiniteStack.New();

Now the ExpressionStack is a properly created object and available for providing services like push, top, and pop. The generator, FiniteStack.New is shown below. This and the method definitions will appear in the implementation file associated with the finite stack interface file. This file will be named FiniteStack.m3. It will begin with a heading indicating the module name and list any necessary imports. Likewise it will end with a statement block (here empty), followed by the module name and a period. The initialization method initStack is also shown below. It merely guarantees that the stack is empty.


MODULE FiniteStack
EXPORTS FiniteStack;
IMPORT Object;

PROCEDURE New(): T =
	VAR result: T;
BEGIN	
		result := NEW(T);
		Object.FailNil(result);
		EVAL result.initStack();
		RETURN result;
END New;

PROCEDURE InitStack(self: T) =
	self.fTop := -1;
END InitStack;

...
BEGIN 
END FiniteStack.  

Because safe creation of an object depends on executing both the call to the standard NEW routine,checking that the call succeeded, and also executing some initialization routine, we always include one or more object generator functions, which return properly initialized instances of the associated class. Note that it calls ordinary procedures NEW and Object.FailNil. It then sends the result variable the initialization message. Since this method is a function, however, we execute it in an EVAL statement, since we don't need the returned value, which is actually the same object as result itself. This procedure New may be taken as a template for the creation of generator functions in all of our classes. The only difference is the specific initialization method employed.

We then perform safe creation and initialization of the reference variable expressionStack simply with


	ExpressionStack := FiniteStack.New();

Generator
	An function that returns a newly created and initialized object in some class.  
	Sometimes this is an ordinary function, and sometimes it is a method of 
	another class. In this library objects are always created with generators.  

At some point within a client program, we may wish to push an expression CurrentExpression onto this stack. When ExpressionStack was created its type was declared to be FiniteStack.T so that it had access to the method push. The client then sends the message


	ExpressionStack.push(CurrentExpression);

The object will receive this message and will execute its method push, with parameter CurrentExpression. This method, as it is executed by ExpressionStack will modify the internal state of the ExpressionStack object while maintaining the stack protocol. In this case the "instance variable" named fTop will be increased and then used a subscript into the other instance variable, named fElements. The insertion is at location fTop of fElements.


PROCEDURE Push(self: T; o: REFANY) =
BEGIN
	IF self.fTop < FiniteStackMax THEN
		INC(self.fTop);
		self.fElements[self.fTop] := o;
	END;
END Push;

Then if this or some other client immediately sends the message


	SomeExpression := ExpressionStack.top();

to ExpressionStack it will produce the thing most recently pushed, which here would be CurrentExpression.


PROCEDURE Top(self: T): REFANY =
BEGIN
	IF self.fTop > -1 THEN
		RETURN self.fElements[self.fTop];
	ELSE
		RETURN NIL;
	END;
END Top;

We remove an item from the stack with pop, which does not return a value:


PROCEDURE Pop(self: T) =
BEGIN
	IF self.fTop > -1 THEN
		DEC(self.fTop);
	END;
END Pop;

We have learned that an object is just a variable whose type is a class type, and that a class is just a type defined by using the OBJECT keyword. We get access to objects using reference variables. The class definition declares instance variables and methods. Some methods are procedures and some are functions which produce a value that can be used in assignment statements or elsewhere. We have not described the interface Object and its main class Object.T, although we have used it to help declare finite stacks. It is defined in the interface file Object.i3. We will describe it below in the section on inheritance.

In object oriented programming it is important to adopt the attitude that the objects are autonomous and responsible for their own behavior. An object behaves by executing a method defined within its class. The instance variables are changed only by the methods, not by other procedures. When a method is executed it is said to be "executed by the object itself." This view is very important to understanding object oriented programming. An object is like an actor whose "part" consists of a few methods. It is given cues (messages) by other actors (objects) and each cue makes it perform (execute a method).

Object Principle

Objects are autonomous actors. They respond to messages by executing methods. The execution of a method may require sending additional messages

Objects are defined by means of Classes. A class describes both the implementation (instance variables) and the interface or protocol (methods) of the objects which belong to it. A class is like a factory because it can create objects according to a plan.

Class Principle

Classes are both collections and descriptions of objects. A class provides a template, or factory, for creation of objects of the same kind.

Within one system we may have several finite stacks, each receiving messages and each executing its own methods. All may belong to the same class. When a finite stack executes push or pop its fTop instance variable will change, but otherwise no change will be made to fTop . This control over changes to fTop will let us guarantee to users of FiniteStack.T that it will indeed behave as a stack, using LIFO protocol. If the clients could modify fTop directly we could not make the guarantee. The finite stack interface file, and the FiniteStack.T type (Class) definition is supposed to be the complete description of the behavior of a finite stack. Only the methods declared are to modify an object in this class.

Unfortunately, the interface shown above does not guarantee that fTop and fElements cannot be modified by clients. After all, they are declared in the same interface as the methods. Therefore, they have the same visibility and access as the methods. This is undesirable and we need a way to prevent clients from making changes that could break the rules. One solution is to break the interface for finite stacks into two interfaces, the first giving the public details and the other filling in the rest. Thus, an improved interface file for finite stacks is as follows.


INTERFACE FiniteStack;
IMPORT  Object;

TYPE	
	T <: Public;
	Public = Object.T OBJECT
		METHODS
			push(o: REFANY);
				(* Insert d into the storage.  No op if the structure is full. *)
			pop();
				(* Remove the most recently inserted object.  No Op if empty. *)
			top(): REFANY;
				(* Return a reference to the last object inserted into the storage. *)
			empty(): BOOLEAN;
				(* True if there are no objects in storage. *)
			full(): BOOLEAN;
				(* True if there is no room for additional objects to be stored. *)
		END;

	PROCEDURE New(): T;

(* Objects are inserted into the finite stack using push. They are removed and 
retrieved with pop and top respectively.  The behavior is last in-first out 
(LIFO), meaning that the most recently pushed object will be the next popped.  
NIL will be returned if the client attempts to pop an empty finite stack, and 
attempting to push onto a full one results in no operation (no-op) 

FORMAL RULES
	1.  Initialization must be done once for any stack object. This is done 
		correctly by FiniteStack.New.
	2.  Immediately after initialization, empty returns true and full returns 
		false.  Pop is illegal and results in no-op, and top, while also 
		illegal, returns NIL.
	3.  Immediately after push, empty returns false and full may return true.  
	4.  If full would return true then push is illegal and results in a 
		no-op.  However if push  fails to insert X by reason of executing 
		when full is true, the stack may not properly be LIFO.  
	6.  FiniteStack.New returns a new allocated and initialized (empty) stack.
	7.  At most some fixed finite number (FiniteStackMax ) elements 
		may be stored at any time.  If the stack currently holds 
		FiniteStackMax elements then full returns true.  
*)

END FiniteStack.

Notice now that if a user imports this interface they have no access to fTop or fElements, as they are not shown here. We indicate that the major class FiniteStack.T is a subclass of FiniteStack.Public, which has the protocol shown. If we do not protect the instance variables in such a way then the language will not prohibit clients or others from having both read and write access to them. Thus, using our first definition, a client could, indeed, say


	ExpressionStack.fTop = 99;

which would cause a lot of trouble. This really should not be possible, as the creator of FiniteStack should have better control over changes made, as she or he wants to ensure that it behaves in a LIFO manner. Therefore we protect our instance variables .

The additional details are revealed in another interface, which we call PRFiniteStack.i3 (for Private FiniteStack).


INTERFACE PRFiniteStack;
IMPORT  Object, FiniteStack;

CONST FiniteStackMax = 20;

REVEAL	
	FiniteStack.T = FiniteSTack.Public BRANDED OBJECT
			fTop: INTEGER:
			fElements: ARRAY [0.. FiniteStackMax-1] OF REFANY;
		METHODS
			initStack()	:= InitStack;
		OVERRIDES
			push		:= Push;
			pop		:= Pop;
			top		:= Top;
			empty		:= Empty;
			full		:= Full;
		END;

	PROCEDURE InitStack(self: FiniteStack.T): FiniteStack.T ;
	PROCEDURE Push(self: FiniteStack.T; o: REFANY) ;
	PROCEDURE Pop(self: FiniteStack.T)
	PROCEDURE Top(self: FiniteStack.T): REFANY ;
	PROCEDURE Empty(self: FiniteStack.T): BOOLEAN ;
	PROCEDURE Full(self: FiniteStack.T): BOOLEAN ;

END PRFiniteStack.

An ordinary client will import only FiniteStack and will have access to all of the methods of FiniteStack.Public applied to a FiniteStack.T object returned by FiniteStack.New. The rest of the details are not needed by ordinary clients. Notice that in interface PRFiniteStack we must qualify the names declared in FiniteStack.i3 with the name of that module. This is because the module names differ. When a module EXPORTS a module it doesn't need to use qualified names, but when an interface or module meerely IMPORTs an interface it does need to use qualified names.

The module implementation for finite stacks needs to export both of these interfaces and is shown below. It is stored in file FiniteStack.m3.


MODULE FiniteStack
EXPORTS FiniteStack, PRFiniteStack;
IMPORT Object;

PROCEDURE New(): T =
	VAR result: T;
BEGIN	
		result := NEW(T).initStack();
		Object.FailNil(result);
		EVAL result.initStack();
		RETURN result;
END New;

PROCEDURE InitStack(self: T) =
	self.fTop := -1;
END InitStack;

PROCEDURE Push(self: T; o: REFANY) =
BEGIN
	IF self.fTop < FiniteStackMax THEN
		INC(self.fTop);
		self.fElements[self.fTop] := o;
	END;
END Push;

PROCEDURE Top(self: T): REFANY =
BEGIN
	IF self.fTop > -1 THEN
		RETURN self.fElements[self.fTop];
	ELSE
		RETURN NIL;
	END;
END Top;

PROCEDURE Pop(self: T) =
BEGIN
	IF self.fTop > -1 THEN
		DEC(self.fTop);
	END;
END Pop;

PROCEDURE Empty(self: T): BOOLEAN =
BEGIN
	RETURN self.fTop <= -1;
END Empty;

PROCEDURE Full(self: T): BOOLEAN =
BEGIN
	RETURN self.fTop >= FiniteStackMax;
END Full;

BEGIN
END FiniteStack. 

Excerpts from a client program are shown below. We have left out details of the expression class type for simplicity. However it is important that the expression type have REFANY as an super type so that expressions may be pushed onto a stack.


MODULE Main;
IMPORT Object, FiniteStack, Expression;

VAR
	expressionStack, auxiliaryStack: FiniteStack.T;
	left, right: Expression.T; // left and right operands of an operator

BEGIN
	expressionStack := FiniteStack.New();
	...
	expressionStack.push(left);
	...
	auxiliaryStack.push(expressionStack.top());  
		/*push the top of expressionStack onto the auxiliaryStack */
	...
	right := auxiliaryStack.top();
	right.writeIt(Stdio.stdout); //output the expression right
	...
END Main;

3.2 Object References and Aliases

When we declare a variable to have a class type, we say that it is a reference to an object. Thus expressionStack, used above is a reference to an object. The object itself was created with the call to NEW within FiniteStack.New. Objects are created only in this way. However it is possible to have several variables referencing the same object. For example in the program excerpt shown at the end of the last section it would be perfectly OK to say:


auxiliaryStack := expressionStack;

It is very important to note that this statement does not create a new stack. It simply makes auxiliaryStack reference the same stack that expressionStack does. The names expressionStack and auxiliaryStack are said to be aliases. Then if we use either alias in a push or a pop, the stack that they reference will be affected. If we push using one of the names and top with the other the object just pushed will be retrieved.

There are two very different ideas about what it might mean for two objects to be "the same." On the one hand we might mean that two objects are the same if they have the same type and the instance variables in them have the same values. This is called identity by value, and it is not what we generally want to mean by "sameness." A stronger notion, object identity, holds only when the two objects are, in fact, one and the same. Said another way, if we have two references to objects, we say that they are the same provided that they reference the same object.

This use of references is very important and intimately bound up with our ideas of abstraction and object independence. It is also the normal thing that Pascal or Modula-3 does when you make assignments using variables which have simple types. If counter has integer type then


	counter := 0;
	...
	counter := counter + 1;

first makes counter a reference to an integer 0. Then it makes counter a reference to some other integer, perhaps 1. Importantly, the first statement does not create a new ZERO. Zero is a mathematical concept in the first place, and a bit pattern in the second. We may put a copy of this bit pattern in a new place, but we don't create a new bit pattern or a new integer. The second statement does not change ZERO, and it does not create a new integer. It simply makes counter reference a different integer, one computed from the integer currently referenced.

Notice how different things are when we use Modula-3's structured types, like arrays and records. If scores and backup have the same array type such as ARRAY[1..100] OF CHARACTER then


	backup := scores;

does not make backup a reference to the same array that scores references. The entire array is actually copied into the new location, so that we have two arrays that currently contain the same data. If we modify one of them, say with


	scores [5] = xxx;

then one of the arrays is changed, but the other is not. Structured types are like little blocks of data that can be moved around and copied and changed from the outside. Simple types don't behave like that and neither do objects. In this regard, and others, objects are more like simple types than like structured types.

There is an important difference, though, between simple types and objects. If an element has a simple type, as 5 is an element of INTEGER, it cannot be changed. Five is five. Variables referencing it can be changed to reference other things but that is different. Five cannot be changed.

Objects can be changed, but ideally they cannot (or at least should not) be changed from the outside. An object can only be changed by sending it a message, using some name that currently references it and the names of one of its methods. Any name that references the object will do. Then if currentScore and lastValue were references to objects then the assignment


	lastValue := currentScore;

would produce lastValue as an alias of currentScore, and the assignment


	currentScore[5] := xxx;

would be illegal as it is an attempt to directly modify currentScore. Instead we would say


	currentScore.atPut(5,xxx);

sending the atPut message to currentScore, and the change would be effective for the object and hence affect all names (variables) that referenced it. Figure 3.2 shows an object and two references ("aName" and "anAlias") by which we may get access to it. These two references are aliases of each other.

In some way we can think of objects as being "out there", living quiet lives, going about their business. And we can think of variables as being distinct from them but giving us a handle by which we can send them messages. They will respond to our messages, changing their state or behavior, but not disrupting it, we hope.

In a certain way objects are like people. People are distinct from their names, may have several names (aliases), respond to messages, are not directly modified from without, and they provide and require services.

Programming with aliases may take a bit of getting used to and there are some problems associated with it. For example, it is possible to create aliases without the knowledge of a client. Then when a change is made using one alias, the change will show up when using the other, and this will be a surprise if you thought a new object was created rather than an alias. There are many conceptual advantages of aliases however, and this is the reason that they have become a fundamental technique in object oriented programming.

For example, suppose we consider the expressionStack again, and suppose the "expressions" we push onto the stack are actually arithmetic expressions as we use in algebra. Also suppose that such expressions have a method simplify which they use to simplify themselves. The operation of the method changes the internal state of an expression object so that its new representation is an algebraic simplification of its former representation. Suppose we execute:


	anExpression := expressionStack.top();
	anExpression.simplify();

Since we are using aliasing, the object anExpression is actually still in the stack (we didn't remove it) but we have caused that very object to become simplified. If the assignment operator simply copied the representation of an object to a new location then the simplify would be performed on the data at the new location, and the object in the stack would not be modified. Instead, with aliases, we operate on a single object. Therefore we don't need to put the object back into the stack after performing simplify because it was never removed.

Objects can even send messages to themselves. Often a method for some class will want to execute another method in the same class. This is done by sending a message to itself. The syntax for it uses the parameter self together with a method name as in self.methodName(), with any required parameters. It is understood that the message is being sent to object executing the current method. Actually, there is nothing magic about the name self that we use as the first parameter in our methods. The only thing that is important is that its type is that of the class of the method.

3.3 Inheritance: Start at the Top

In defining the class FiniteStack.T above, we did not show the details of the Object.T class on which it depends. The declaration of this class is contained in the interface Object.i3 shown below:


INTERFACE Object;
	IMPORT Wr;

EXCEPTION fatal;

TYPE T = OBJECT
	METHODS
		writeIt(f:Wr.T)		:= WriteIt; 
		assert(a:BOOLEAN) RAISES {fatal}	:= Assert;
		shallowClone():T	= ShallowClone; (* DO NOT OVERRIDE *)
		clone():T		:= Clone;
		copyFields(o:T)		:= CopyFields;
		equal(o:T):BOOLEAN	:= Equal;
		END;

PROCEDURE Error(s:TEXT) RAISES {fatal};
PROCEDURE FailNil(o:REFANY) RAISES {fatal};

	PROCEDURE WriteIt(self:T; f:Wr.T); 
	PROCEDURE Assert(self:T;a:BOOLEAN) RAISES {fatal};
	PROCEDURE Equal(self:T; o:T):BOOLEAN ;
	PROCEDURE ShallowClone(self:T):T ;
	PROCEDURE Clone(self:T):T ;
	PROCEDURE CopyFields(self:T; o:T) ;

	PROCEDURE DoWrite(f:Wr.T;  o:REFANY);
	PROCEDURE AreEqual(m,n: REFANY):BOOLEAN;

END Object.

The associated implementation file Object.m3 is:


MODULE Object;
IMPORT  Wr, Thread, Stdio, Text, RTAllocator (*, CopyBytes *) ;

PROCEDURE comment(t:TEXT) =
BEGIN
	TRY
	Wr.PutText(Stdio.stdout, t);
	Wr.PutChar(Stdio.stdout, '\n');
	Wr.Flush(Stdio.stdout);
	EXCEPT
		| Thread.Alerted =>
		| Wr.Failure => 
	END;
END comment;

	PROCEDURE DoWrite( f:Wr.T;  o:REFANY) =
	BEGIN
	IF f = NIL THEN f := Stdio.stdout; END;
	TRY
		IF o = NIL THEN
			Wr.PutText(f, "NIL ");
		ELSIF ISTYPE(o,T) THEN 
			NARROW(o,T).writeIt(f);
		ELSIF ISTYPE(o,TEXT) THEN
			Wr.PutText(f, NARROW(o, TEXT));
		ELSE 
			Wr.PutText(f, "aValue "); 
		END;
	EXCEPT
		| Thread.Alerted =>
		| Wr.Failure => 
	END;
	END DoWrite;

	PROCEDURE AreEqual(m,n: REFANY):BOOLEAN =
	BEGIN
		IF m = NIL THEN
			RETURN FALSE; (* or RETURN n = NIL ?? *)
		ELSIF ISTYPE(m,T) THEN 
			RETURN NARROW(m,T).equal(n);
		ELSIF ISTYPE(m,TEXT) THEN
			IF ISTYPE(n,TEXT) THEN
				RETURN Text.Equal(m,n);
			ELSE 
				RETURN FALSE;
			END;
		ELSE
			RETURN m = n;
		END;
	END AreEqual;

	PROCEDURE Error(s:TEXT)  RAISES {fatal} =
	BEGIN
	 TRY
		Wr.PutText(Stdio.stderr,s);
	EXCEPT 
		| Thread.Alerted =>
		| Wr.Failure => 
	END;
	RAISE fatal;
	END Error;

	PROCEDURE FailNil(o:REFANY) RAISES {fatal} =
	BEGIN
		IF o = NIL THEN Error( "A Reference is NIL."); END;
	END FailNil;

	PROCEDURE WriteIt(self:T; f:Wr.T) = 
	BEGIN
	IF f = NIL THEN f := Stdio.stdout; END;
	TRY
		Wr.PutText(f," An Object ");
	EXCEPT
		| Thread.Alerted =>
		| Wr.Failure => 
	END;
	END WriteIt;

	PROCEDURE Assert(self:T;a:BOOLEAN) RAISES {fatal}=
	BEGIN
		IF NOT a THEN Error( "Assertion Failed."); END;
	END Assert;

	PROCEDURE Equal(self:T; o:T):BOOLEAN =
	BEGIN
		RETURN self = o;
	END Equal;      

	PROCEDURE ShallowClone(self: T):T =
	VAR result := self;
	BEGIN
		result := RTAllocator.NewTraced(TYPECODE (self));
	(*      CopyBytes.P(self,result,ADRSIZE(self)); *)
		result.copyFields(self);
		RETURN result;
	END ShallowClone;

	PROCEDURE Clone(self: T):T =
	BEGIN
		RETURN self.shallowClone();
	END Clone; 

	PROCEDURE CopyFields(self:T; o:T) =
	BEGIN
		(* Nothing *)
		(* Copy all fields of o into fields of self *)
	END CopyFields; (* Override whenever you add new fields to an heir. *)

BEGIN
END Object.

These methods will be discussed shortly. Some of them are merely templates for expansion. All classes in this library are descendants of Object.T. There are two reasons for this. The first is that we may guarantee that all objects will then have some basic functionality, such as is provided by writeIt. More importantly, however, will be that we will be able to form lists and sets and trees, etc., using any of these objects as the contents. We will be able to construct lists of trees as easily as we can create lists of integers. This will be discussed again when we talk about collections.

The class Object.T was used in two different ways in FiniteStack.T. First as an ordinary type: some of the methods of FiniteStack.T had parameters or return values that can be of this type. This is because Object.T is a class type and hence a subtype of REFANY. Thus we can have a finite stack of elements of type Object.T. Second, and more importantly though, the FiniteStack.T class itself was defined to be a subclass of the Object.T class. This was done by the occurrence of Object.T as the parent class in the declaration of the public part of FiniteStack.T.


T <: Public;
Public = Object.T OBJECT 
	...
END;

We say that FiniteStack.T inherits from FiniteStack.Public, which in turn inherits from Object.T. Thus, we say that FiniteStack.Public (and indirectly, FiniteStack.T) is derived from Object.T, or is a subclass of Object.T. By that we mean that every object in FiniteStack.T is automatically a member of Object.T. It also means that every reference to an FiniteStack.T is compatible with a reference variable of type Object.T. Also, since Object.T is a subclass of ROOT, which is itself a subtype of REFANY, we know that every variable of type FiniteStack.T is actually a pointer variable.

Inheritance provides a mechanism which permits an object to belong to more than one class. Thus it provides a way for the sets represented by the classes to overlap. If FiniteStack.T inherits from Object.T, then all objects of class FiniteStack.T are objects of class Object.T, so that FiniteStack.T is contained in Object.T.

Inheritance

Using one class to define another by declaring only the new features. Defines a subclass relationship between the original (parent) class and the new class. All objects in the new class are members of the old class.

Notice that Object.T has no instance variables and it has a few methods, most of which don't do much. It is not intended that many variables will be given the type Object.T, or any objects created with the type, so Object.T is an example of what is called an abstract class. If it were not an abstract class we would have included a generator function Object.New() so that such objects would be easy to create.


	Abstract Class

	An actual class which is the union  of all of its subclasses.  
	It has no objects which are not also objects of some subclass.  
	In our library it may be recognized by the absence of a generator 
	function.  


	Concrete Class

	An actual class which may contain objects not in any subclass.  
	May be recognized by the presence of a generator function.  

The class FiniteStack.T (which is concrete, as evidenced by its generator function) is called a subclass (sort of like subset) of Object.T, and things become objects in an abstract class like Object.T only by being created within some concrete subclass like FiniteStack.T. If we declare a new class to be a subclass of FiniteStack.T then objects in the new class will be instances of that class, as well as instances of FiniteStack.T and Object.T. Each class may declare itself to be a subclass of one other class (or no class at all, but for us only Object.T will declare no "ancestor") and so classes in Modula-3 form a hierarchical structure defined by inheritance. The structure is actually a tree with edges represented by arrows pointing at the ancestor classes. For example we shall define Fraction.T below as another subclass of Object.T and we could (but won't) define Integer.T as a subclass of Fraction.T. Fraction.T will treat fractions with numerators and denominators as objects. Integer.T would treat ordinary integers as objects. We would then have a hierarchy as shown in Figure 3.3.

Alternatively we could show subclasses in the same way that we ordinarily show subsets as in Figure 3.4.

This last picture demonstrates the general idea that we think of objects in class Fraction.T as just special types of objects of type Object.T, and elements of Integer.T as just special instances of Fraction.T.

Since we can directly inherit from only one class, we say that Modula-3 is a single inheritance language. Similarly, Object Pascal and Smalltalk are also single inheritance languages, but C++ has multiple inheritance where a class may declare several immediate ancestors. With single inheritance the structure of the classes is hierarchical or treelike, with some class as the root of the tree or the ultimate ancestor of all classes. If multiple inheritance is possible the inheritance structure is a directed graph with no cycles rather than a tree.

Notice that inheritance is transitive, so that Integer.T being a subclass of Fraction.T, and Fraction.T a subclass of Object.T, it follows that Integer.T is a subclass of Object.T. For any given class, the ancestors form a chain back to ROOT. For any given class the descendants of that class form a tree spreading out from the class.

When one class (the sub-class) inherits (directly or indirectly) from another (the super-class), the objects of the class get methods and instance variables from the super-class and hence from all of its ancestors. Thus if Object.T declared an instance variable ident then all objects in class FiniteStack.T would have such an instance variable as well. Likewise, objects in FiniteStack.T do have a method, writeIt, and another, clone, etc. Therefore, we could have implemented push so that we first check that the stack is not full so that we can halt the computation if it is. We can accomplish this with the assert method defined in Object. To do this, the object executing the push method would have to send the assert message to itself. This can be accomplished from within any method of any class descendant from Object.T by using self as the target of the message.


	self.assert(NOT self.full());

Then the same object, self, a FiniteStack.T object, would execute the assert method, and if the object is indeed full, printing an "assertion failed" message and then halting the execution of the program.

It is possible that an inherited method (one declared by some ancestor) provides inappropriate action in the current class. This is the case here with the writeIt method. If we were to say


	expressionStack.writeIt(Stdio.stdout);

the stack would execute the method it inherited and we would see the string


	An Object

appear in the output. When a method is inherited it is possible to redefine the action that it carries out when executed by an object of the new class. In this case we would redeclare writeIt as an override in the FiniteStack.T class definition, and redefine it in the implementation file of its definition as:


 REVEAL	
	T = FiniteSTack.Public BRANDED OBJECT
		...
		OVERRIDES
			writeIt	:= WriteIt;
		...
	END;

...
PROCEDURE WriteIt(self: T; f:Wr.T) =
BEGIN
	FOR i := self.fTop TO 0 BY -1 DO
		self.fElements [i].writeIt(f);
		Wr.PutText(f," ");
	END;
END WriteIt;

We now send the writeIt message to each object in the stack, writing the stack from top to bottom. Note that we process it from the top down, as this is more in keeping with the idea of what a stack is. This cannot be overemphasized. There is a key principle here:

Principle of Abstraction

The processing provided by a class should always be in accord with the nature of the abstraction on which the class is based.

Inheritance is very powerful if we combine the above ideas with the ideas of indirect or reference variables. Suppose that we have declared and defined the Fraction.T and Integer.T classes discussed above. We shall see below that Fraction.T will declare a method named add. Assume that Integer.T has a method isPrime, which returns a BOOLEAN TRUE if the integer is a prime number.

Updated (but simplified)class photos are shown in Figure 3.5 , with inheritance indicated by arrows between the classes. Note that as we descend the hierarchy, we don't lose the public methods of the ancestors, but we may add additional ones. The deeper we get in the inheritance structure, the more sophisticated our classes, and hence the objects, become.

Then suppose that we have declared two reference variables myFraction and myInteger as in



	myFraction: Fraction.T;
	myInteger: Integer.T;

The properties of reference variables, the nature of object identity, and the fact that subclasses are like subsets, together imply that it is possible to make the assignment


	myFraction := myInteger;

and that myFraction, even though declared to be a name associated with class Fraction.T, is now a reference to an Integer.T. This is indeed the case. Note that we are assuming this integer object was properly created in the first place, with a call to Integer.New.

This integer object, masquerading under the name myFraction, ought to behave just like any other integer. Therefore, if writeIt was overridden in Fraction.T and then again overridden in Integer.T, and if we next say


	myFraction.writeIt(Stdio.stdout);

then the method executed is the version known to the object itself, which is an Integer.T, not the version from class Fraction.T. Thus, in a very real sense the object is seen to be autonomous. We can't get it to behave improperly even if we reference it with a name associated with an ancestor class. See Exercises 11 and 12 <<<<MAY CHANGE>>> at the end of this chapter.


Virtual method

	A method of a class that, when used with a reference variable, is 
	interpreted in the most specific way possible.  The method executed 
	is the method associated with the object itself, not necessarily the 
	name associated with the class of the variable used to refer to the 
	object.  In Modula-3, all methods are virtual.

When referred to by a reference variable, the above Integer.T object should be able to respond to the message ...isPrime, but here we have a difficulty. Modula-3 is very strongly typed, , and it is typed by the compiler, which means that the names are typed. Thus if we try to say myFraction.isPrime(), we will get a complaint from the compiler that myFraction is a reference to type Fraction.T and there is no such method in that class. The compiler actually has no way to know that we have made an assignment "up the hierarchy" and that myFraction will reference an Integer.T at this spot at run time. If the programmer can verify that in every execution of the program myFraction will always reference an Integer.T at this spot then we can get the object to respond to isPrime by Narrowing the name as in:


	NARROW(myFraction, Integer.T).isPrime();

Note that the type name is used as a parameter to the function NARROW, which returns a reference to an object of that type. One way to think of narrowing, sometimes called type casting, is that it simply reassures the compiler that the programmer knows what is best and can verify externally that myFraction always represents an Integer.T here. Actually it does more, as will be discussed shortly. Note that if you use narrowing (we will, in important ways) and you are wrong, and myFraction holds a reference to something not in Integer.T then at run time you will get an error. It is an error that will be caught by the run time system and your program will be halted. Narrowing must be used carefully, and only when one can prove that it is valid. Narrowing can never make a correct program incorrect, nor an incorrect program correct. It must be used only when the programmer is willing and able to take on the burden of correctness.

FiniteStack.T is used to hold, in a structured (LIFO) way, references to objects of other classes. Therefore it is an example of a collection class. Often we shall want to push objects from several classes onto a single stack. This is why the element type for the elements array was declared to be a REFANY, which means any reference, including any object reference. When we pop the objects off the stack we get things whose class is not known to the compiler, other than as REFANY. We shall need to narrow them so that the compiler will let us operate on them after they are removed.

This ability to use a single name, such as myFraction, to reference different kinds of things is a special kind of strong typing. Standard "strong typing" (such as found in Pascal) is very strict about every name, with everything having only a single type. The same is actually true here with the difference being that in Modula-3 the types are not disjoint. A class type consists of all objects in a class and in all subclasses of that class. Assignment compatibility is consistent with this view. If a name is declared to be associated with a class type, then it can refer to any object in that class or in any subclass of that class. We say that Modula-3 introduces type polymorphism into the language, as we may associate "many forms" or types with a name.


	Type Polymorphism

		The ability of a variable to refer to objects of many 
		different types.  

It is important to note that both variables and objects have types. The type of a variable is decided by the programmer who gives a declaration. The compiler sees this declaration and associates a type with the variable name. From then on, that name may be associated only with that type (or a subtype). Importantly, typing of variable names is at compile time. On the other hand, objects are given a type when they are created using the built-in function NEW. The parameter used with NEW is a type that is assigned to the newly created object. From then on, the object may be referred to by many different names (aliases), but its type never changes. An object always has a single type. The names that refer to it are names associated with its own type (creation type) or the types of superclasses of that type. Importantly, typing of objects is at run time.

Continuing the discussion of Object.T, some of its methods are specifically included for no other purpose than to be overridden. The method writeIt is such a method. Normally every class should override writeIt, and so (as we never directly create objects of type Object.T) we should never execute the method. It is here to provide a protocol which other inheriting classes will use in a consistent way. If every class declared a method to show an external representation of the object, but all used different names, it would be difficult to remember what name was used in what class. We are thus programming here according to the Rule of Seven discussed earlier. Some methods, especially here in the root class, are intended never to be overridden. Examples are shallowClone and assert. Others, such as copyFields are intended to be overridden in every class. The method Clone is provided so that we may override it as necessary to create objects identical to the one that executes the method.

The file Object.m3 also declares two public procedures that are not methods: FailNil and Error. FailNil is here because a call to NEW may fail. It is used within generator functions as:


	result := NEW(T);
	Object.FailNil(result);
	. . .

and causes a structured shutdown of the program rather than a crash if the heap is out of room. Note that it can't be a method, as it is called in situations in which there may be no object to execute it. We similarly provide the ordinary function Error, which writes out its string parameter and the program is halted. It allows the programmer to shutdown a program when the program detects an error from which it cannot recover.

Finally, in this section we look at just what is inherited, and what is not. In terms of the definition of Modula-3 what is inherited is the physical structure of the instance variables, the method declarations, and for those methods not overridden, the code of the inherited methods. It is not possible to drop an instance variable or a method name in a subclass, nor is it possible to change the type of an instance variable, or any parameter of an inherited method. We can add new stuff, or we can redefine the code for a method. That is all.

The above description is very physical, and could be misleading to the user attempting to develop a good style of object oriented programming. Conceptually we want to think that we are inheriting much more than just code and bits. Specifically, when we define an FiniteStack.T class, we declare that it exhibits a LIFO protocol, and we are careful to specify how that is interpreted in terms of the actions of push and pop. If we create a subclass of FiniteStack.T the subclass is supposed to be "like a subset" of FiniteStack.T, or in other words, a specialization of FiniteStack.T. This means in particular that subclasses of FiniteStack.T should still be LIFO, and as the name is FiniteStack.T, they should also be finite. Another way to say this is to say that the relationship between a subclass and its superclass is ISA, pronounced "is a". Thus, in the above, an Integer.T ISA Fraction.T, and a mathematician would agree. Nothing in the Modula-3 language definition or implementation enforces this however. It is necessary for the programmer, then, to be aware of what he or she is doing when using inheritance, and generally speaking to ensure that a subclass is really a specialization of its superclass.


	ISA

	A relationship between a subclass and its superclass.  Other 
	relationships are possible but this is the norm.  An object of a 
	subclass ISA (is a) object of its superclass.  It exhibits all of the 
	properties of a superclass object.  Implies that the subclass is 
	a specialization of the superclass.  

Occasionally it is useful and even necessary to program as if "ISA" weren't the norm. In these cases it is important to be clear in the documentation of a class that it is only "LIKE" its superclass. Sometimes we need to create some software in a hurry, and we look at the stuff we already have, and find that some class does most of what we want. We may need to look at the code to determine this, although the specifications should be adequate. Then, to create the new stuff, it is tempting and useful to subclass the existing class, ignoring the intent and the specification of the original, but only taking advantage of the code. Then the subclass objects are only "similar" to the superclass objects, and we say that the relationship between the subclass and superclass is ISLIKE, rather than ISA. For example it is possible to implement a queue (first in-first out, FIFO structure) by subclassing a stack, but queues aren't specialized stacks, but only similar to stacks. The programmer needs to be careful that she/he recognizes when each technique is being used, with ISA being more important. The reason for this comes back to the Rule of Seven. The more similarities there are, the less has to be remembered. If our class hierarchy is a specialization hierarchy, with each class adding behavior, but not changing it, then we achieve a conceptual unity that will help us use what we have and avoid the errors that come from misunderstanding what we do.


	ISLike

	A relationship between a class and its superclass.  A weaker 
	relationship than ISA.  It is used only for code reuse rather than 
	as an abstraction mechanism.  Implies that the subclass is a 
	modification of its superclass.  Some item of the semantics has 
	changed.  

3.4 Collections and Iterators

The class FiniteStack.T discussed above is actually an example of a general type of class called a collection. The idea of a collection is that it is a container of some sort for objects (or data). Examples of collections are sets, lists, stacks, arrays, dictionaries, and several others. In fact, what we shall be collecting in our collections are references to values, usually objects. Since we may have several references to the same object, the same object can be a member of several collections simultaneously. An object can also be processed by some function while it is a member, so that we don't need to remove elements from sets or lists to manipulate them. An implication of this is that removal of an object from a collection never implies its destruction; it may still be referenced from other places in the program. Since the collections store references to objects rather than copies of objects we don't have to worry about updating all copies of some information when one copy changes. Since we permit several aliases, the owner of a piece of information need not give up access to an object when inserting it into a list or stack.

Since a collection is a general idea, it can be specialized. And since classes are a way to implement specialization it will be useful for us to create a class Collection.T. Most of the classes that we discuss in this book are either subclasses of Collection.T or of another major grouping, Magnitude.T, which is discussed in the next section. Since collections "store" objects we shall need ways to get objects into a collection, ways to get them out, and ways to find out what information is in the collection. Different subclasses will use different methods, with different names, for their primary insertion and deletion operations. For stacks the operations are traditionally called push and pop, for queues they are called enqueue and dequeue. But all collections exhibit an "inserting" and a "removing" behavior. Therefore the class Collection.T will need a method insert, and a method remove, which will be inherited by all subclasses, and will be overridden by most of them.

The "things" to be inserted into our collections will always be of type REFANY, or references to values. Most of the uses of collections in this book will use objects descended from Object.T. This has benefits and a cost. The benefit is that we may have lists or stacks of anything that we build here, since they will all derive from Object.T. We may even have stacks of lists or lists of stacks. We can even have heterogeneous collections such as a stack that simultaneously contains fractions and lists. We don't need to commit to some particular type, since the types are extendible by inheritance. The cost is that if we wish to collect something that is not a REFANY we will have to build a special structure to serve as a "package" of the thing that we really want. One easy way to do this is to create a class, derived from Object.T, that has the value as an instance variable.


INTERFACE Collection;
IMPORT Object, Wr;

TYPE   (* Three Abstract Classes *)

	ActionProc = PROCEDURE(o: REFANY);
	TransformProc = PROCEDURE(o: REFANY):REFANY;
	PredicateProc = PROCEDURE(o: REFANY):BOOLEAN;
	
	T = Object.T OBJECT (* Collection of REFANY objects *)
		METHODS
			insert(o:REFANY) ;	(* 	:= NIL  *)
			remove(o:REFANY) ;	(* 	:= NIL  *)
			newIterator():Iterator ;(* 	:= NIL  *)
			eachDo(DoIt:ActionProc) ;(* 	:= NIL  *)

			firstThat(checkIt:PredicateProc):REFANY	:= FirstThat;
			select(c:T; checkIt:PredicateProc)	:= Select;
			reject(c:T; checkIt:PredicateProc)	:= Reject;
			collect(c:T; transformIt:TransformProc)	:= Collect;
			empty():BOOLEAN				:= Empty;
			element(o:REFANY):BOOLEAN		:= Element;
			cardinality():INTEGER			:= Cardinality;
		OVERRIDES
			writeIt		:= WriteIt;
		END;

	PROCEDURE FirstThat(self:T; checkIt:PredicateProc):REFANY ;
	PROCEDURE Select(self:T; c:T; checkIt:PredicateProc) ;
	PROCEDURE Reject(self:T; c:T; checkIt:PredicateProc) ;
	PROCEDURE Collect(self:T; c:T; transformIt:TransformProc) ;
	PROCEDURE Empty(self:T):BOOLEAN ;
	PROCEDURE Element(self:T; o:REFANY):BOOLEAN ;
	PROCEDURE Cardinality(self:T):INTEGER ;
	PROCEDURE WriteIt(self:T; f:Wr.T) ; 

TYPE 
	Iterator <: PublicIterator;
	PublicIterator = Object.T OBJECT
		METHODS
			initIterator(c: T): Iterator;
			nextItem(VAR o: REFANY):BOOLEAN ; (* 	:= NIL  *)
			reset() ; (* 	:= NIL  *)
			short();
			done():BOOLEAN;
		END;

	Position <: PublicPosition;
	PublicPosition = Object.T OBJECT
		METHODS
			initPosition(c: T):Position;
			reset() ; (* 	:= NIL  *)
		END;

END Collection.

Note that the insert and remove methods were marked in comments as ":= NIL." This is the way we indicate a function that has no body and which is intended entirely to be overridden in subclasses. The presence of such functions indicates that Collection.T is abstract. Modula-3 in fact, will not let us create such objects directly, we can only create objects of subclasses in which all of the unimplemented methods have been redefined. The reader will also note that there is no NewCollection generator function. There will be no objects in this class that are not also in some subclass. That doesn't mean that the class is useless. It serves two important purposes. First, it defines a protocol of method/message names that will be preserved by all subclasses. For example if we were to improve our FiniteStack.T class from the above, we would want to make it a subclass of Collection.T. Then we would want insert to be implemented within FiniteStack.T as a call to push. That way we would get the advantage of the generality of an insert method, while strictly maintaining the LIFO protocol of a stack.

The second purpose served by the class is that we can include here some very useful code, which will be inherited and probably not overridden, and which all subclasses will find useful. We shall show how writeIt can be written here to guarantee that any collection will write out all of its elements. The method empty is an example of this, being implemented in terms of cardinality. Before we can describe the implementation we need to discuss another fundamental idea.

Collections being containers of many things, it is often necessary to process all of the elements of a collection in some way. For example to count or write the objects in a collection we need to access them as a whole, without disturbing the contents of the collection. The thing that makes this possible is the idea of an iterator.


Iterator

	A mechanism for sequentially processing  the elements of any 
	collection.  Iterators generalize the operation of FOR loops 
	on array structures.  

Iterators are the means of processing the elements of a collection one at a time until all have been used. Usually this means sending some message like writeIt to each element in the collection, or possibly sending each object of a collection as a parameter to some processing procedure. This idea is embodied in the abstract class Collection.Iterator. Each subclass of Collection.T (Lists.T for example) will have an associated iterator class (in this case Lists.Iterator), which is a subclass of Collection.Iterator. The declaration of the iterator protocol follows, extracted from the Collection interface above is:


TYPE 
	Iterator <: PublicIterator;
	PublicIterator = Object.T OBJECT
		METHODS
			initIterator(c: T): Iterator;
			nextItem(VAR o: REFANY):BOOLEAN ; (*   := NIL  *)
				(*  If all items have been yielded then return FALSE.  Otherwise return TRUE
					and set o to the next unyielded item of self. *)
			reset() ; (* 	:= NIL  *)
				(*  Reset to the beginning as if no elements had yet been yielded. *)
			short();
				(*  Short-circuit the iterator so that nextItem will next return FALSE. *)
			done():BOOLEAN;
				(*  TRUE if all items have been yielded (or none were initially present). *)
		END;

As indicated by this declaration, There is additional hidden information needed to implement iterators. The same is true of the Collection.Position class. This information is given in the private interface, PRCollection.i3.


INTERFACE PRCollection;
IMPORT Object;
FROM Collection IMPORT T, Iterator,Position,PublicIterator,PublicPosition;

REVEAL 
	Iterator = PublicIterator BRANDED OBJECT
			fCollection : T;
			fDone : BOOLEAN;
		OVERRIDES
			initIterator := InitIterator;
			short		:= Short;
			done		:= Done;
			writeIt		:= WriteIter;
			copyFields	:= CopyIterFields;
		END;

	PROCEDURE InitIterator(self: Iterator; c: T): Iterator ;
	PROCEDURE Short(self: Iterator; ) ;
	PROCEDURE Done(self: Iterator):BOOLEAN ; 
	PROCEDURE WriteIter(self: Iterator; f, Wr.T) ;
	PROCEDURE CopyIterFields(self: Iterator; o:Object.T) ;

REVEAL 
	Position = PublicPosition BRANDED OBJECT
			fCollection: T;
		OVERRIDES
			initPosition := InitPosition;
			writeIt := WritePos;
			copyFields := CopyPositionFields;
		END;

	PROCEDURE InitPosition(self: Position; c:T):Position ;
	PROCEDURE WritePos(self: Position; f: Wr.T) ;
	PROCEDURE CopyPositionFields(self: Position; o:Object.T) ;

END PRCollection.

Here we have used a different form of IMPORT that permits us to avoid using qualified names for the items specifically imported from Collection. Among other things, the revelation for the Iterator class shows that every iterator object has an fCollection instance variable, which holds a reference to the collection on which the iterator operates. The fDone variable is set to true for an empty collection, and also when the iterator is finished with its work of iterating, or "yielding" each of the elements of its collection.

Within any collection class is a method newIterator, which creates and initializes an iterator of the appropriate kind for that class. This is the only appropriate way to create an iterator. The initIterator method needs to be executed for any iterator, and it is simply:


	PROCEDURE InitIterator(self: Iterator; c: T): Iterator =
	BEGIN
		self.fCollection := c;
		self.fDone := c.empty();
		RETURN self;
	END InitIterator;

Subclasses of Collection.T that provide more specialized iterators will also provide more specialized initializations, but those methods will always call this one. An iterator will always produce the objects in a given collection. This is the collection mentioned in its initialization, and saved in its instance variable. Remember that only a reference is saved. The collection is not copied by this code. The instance variable fDone remembers whether there are any more elements to yield. If the collection is empty the value will be true. Note that an iterator may not operate correctly if a collection is changed after an iterator is initialized (or reset). This means that it is not safe in general to use an iterator as a mechanism for modifying the collection over which it iterates. See Chapter 10 for some ideas about how this requirement may be relaxed. Reset will permit an iterator to be reused.

The objects in the collection are actually accessed by calling an iterator's nextItem method. It is used in the following way in virtually all cases. Suppose that we have a Lists.T subclass of Collection.T (Lists.T is described briefly in chapter 4 and completely in chapter 6), and a Lists.Iterator subclass of Collection.Iterator. Suppose that aList has type Lists.T and it has been created, initialized, and used, so that it contains some objects. Suppose that IT is declared to be of class Collection.Iterator, but that it has only been declared, not created. Suppose that we also have a reference variable aValue declared to be of type REFANY. Then to write all objects in aList we could write:


	IT := aList.newIterator(); (* creates a list iterator*)
	WHILE  IT.nextItem(aValue) DO 
		Object.DoWrite(Stdio.stdout, aValue);
	END;

The repeated sending of the nextItem message will yield successive elements of the list aList, and the function return value will be true as long as there is more to yield. This is very much like the way that a for loop is used to iterate over an array (actually over the subscripts of the array, which form a primitive type of collection). This is a situation in which narrowing comes to our aid. Note that variable aValue has type REFANY, the parameter of nextItem. However, we actually store values with much more specific types than REFANY. If we had been storing only objects in some class Expression.T in the list, and if such objects respond to the simplify message then we could simplify all of them in the list with


	IT := aList.newIterator();  (* creates a list iterator *)
	WHILE  IT.nextItem(aValue) DO 
		NARROW((aValue, Expression.T).simplify();

The narrowing is needed as aValue has a declared type of REFANY, which does not have a simplify method (or any method). Note that it doesn't help to declare aValue as type Expression.T, because then it conflicts with the parameter type of nextItem.

The nextItem method of Collection.Iterator uses a VAR parameter to pass information back from the routine nextItem to its caller. In this way, nextItem gives us access to a new item from a collection each time it is called. The reason that we do not return this item as the function result is that we want to use that for loop control, returning FALSE only when nothing could be returned for the reference parameter.

Note that in the definition of Collection.Iterator we have an instance variable fCollection which has a class type. We can say that an iterator HASA collection. This is yet another relationship between classes. An object in one class HASA object in another if one of its instance variables is in that other class.


	HASA

	A relationship between classes.  The relationship A HASA B is true if 
	B is used to implement A.  One of A's components is an element in B.  
	Sometimes we make a choice   between ISA and HASA when designing 
	a new class.  

Since the class Collection.T is abstract and there will be no objects immediately in this class it has no instance variables to implement a collection. The subclasses will create these. Therefore the insert and remove methods cannot be implemented here


			insert(o:REFANY) ;	(* 	:= NIL  *)
				(*  Insert o into self. *)
			remove(o:REFANY) ;	(* 	:= NIL  *)
				(*  Remove o from self if present. *)

Well, so far we have described how useless Collection.T and Collection.Iterator are and how they can be made useful by creating subclasses. Actually we can show how to implement the method writeIt here, even though we don't know how an actual collection, like a list, stack, or binary tree will be implemented.


	PROCEDURE WriteIt(self:T; f: Wr.T) = 
	VAR It : Iterator;
		o: REFANY;
	BEGIN
		It := self.newIterator();
		WHILE It.nextItem(o) DO
			Object.DoWrite(f, o);
			Wr.PutText(f," ");
		END;
	END WriteIt;

This writes a space separated list of the objects in any collection. Note that all objects inherit or override writeIt, as this method was first declared in Object.T. There is a very subtle but extremely important point to be made about this code. Suppose that writeIt is not overridden in some subclass, say Lists.T, of Collection.T. Then suppose a client sends the writeIt message to aList in class Lists.T. Then aList will be the one executing the method above, and aList will be the one sending itself the newIterator message which appears there. Note that Lists.T will override newIterator (it is a MUST OVERRIDE method). Therefore, aList when executing newIterator will execute its own version, not the version (unimplemented version) in Collection.T. This is in spite of the fact that the code above which is executed IS from class Collection.T. This is an instance of the dynamic binding principle at work, and it is one of the important things that makes Object Oriented Programming special:

Dynamic Binding Principle

Every message that is sent is interpreted in the context of the object executing the method.

Because of this principle we can create a method in a superclass and mention other methods which will be called at run time even though they haven't been created yet. All that is required is that we establish in this class (or a superclass) the protocol for the method to be called. That is, its name, and its parameter list. If an object in some subclass executes this method, it will interpret all messages in the method in light of its own definition, taking into account any overrides of the methods known to the class that actually contains the method executing. Strange perhaps, when you first think about it, but very useful. Also the dynamic binding principle on which it is based is very simple and easy to remember. It is also central to the fundamental idea of object oriented programming, that the objects should be autonomous and in control

We mention the method, copyFields, that was originally declared in Object.T and will be redefined in every class. The dynamic binding principle assures us that if we send some object a copyFields message then that object will respond by copying its own fields, even if that object is currently being referred to by a name associated with a superclass of the actual object. In turn, this will assure us that ShallowClone works properly, and thus that Clone does also. We say that this makes copyFields polymorphic, as it works on many types.

3.5 Magnitudes

The class Magnitude.T embodies our ideas of size and quantity. It has many subclasses, like Fraction.T, Complex.T, and even Integers.T, which treats ordinary integers as if they were objects. The key to magnitudes is the comparison of objects with methods like less and equal. The abstraction on which magnitudes are built is the idea of the partial ordering of a set. A set is in partial order if there is a relation <= between elements of the set which satisfies the following rules


Partial Order 

a) reflexive (a <= a for any a), 
b) transitive (a <= b and b <= c implies a <= c), and 
c) anti-symmetric ( a <= b and b <= a implies a = b).  

A partial order is called total, if in addition to the above, we have

d) for any pair of elements a, b, either a <= b or b <= a.

The integers are in total order with the usual <=. A hierarchical structure, like our class structure (if we consider only inheritance from Object.T) represents a partial order (but not a total order) if we let <= stand for the ancestor-descendant relationship. That is we say class A <= class B if B is an ancestor of A or is the same class.

The class protocol for Magnitude.T is as follows:


INTERFACE Magnitude;
IMPORT Object, Wr;

TYPE
	T = Object.T OBJECT

		METHODS
			less(m:T):BOOLEAN ;
			hash():CARDINAL;

			notEqual(m:T):BOOLEAN			:= NotEqual;
			greater(m:T):BOOLEAN			:= Greater;
			lessEqual(m:T):BOOLEAN			:= LessEqual;
			greaterEqual(m:T):BOOLEAN		:= GreaterEqual;
			between(first:T; second:T):BOOLEAN	:= Between;
			min(m:T):T := Min;
			max(m:T):T := Max;
		OVERRIDES
			writeIt		:= WriteIt;
			equal		:= Equal;

		END;

	PROCEDURE NotEqual(self: T; m:T):BOOLEAN ;
	PROCEDURE Greater(self: T; m:T):BOOLEAN ;
	PROCEDURE LessEqual(self: T; m:T):BOOLEAN ;
	PROCEDURE GreaterEqual(self: T; m:T):BOOLEAN ;
	PROCEDURE Between(self: T; first:T; second:T):BOOLEAN ;
	PROCEDURE Min(self: T; m:T):T ;
	PROCEDURE Max(self: T; m:T):T ;
	PROCEDURE WriteIt(self:T; f: Wr.T) ; 
	PROCEDURE Equal(self: T; o:Object.T):BOOLEAN ;
	
END Magnitude.

This is again an abstract class and will only have objects which are created in some subclass. All subclasses must implement less, equal, and hash, but most of the others are implemented here using less and equal. They are also often overridden by subclasses for efficiency.

Note that Object.T implemented an equal method. In that class objects are equal only if they are the same object (aliases). In magnitudes, we have a fundamentally different idea. Magnitudes are equal if they have the same "size", by the anti-symmetric law. For this reason, equal is reimplemented here as an error call.


	PROCEDURE Equal(self: T; o:Object.T):BOOLEAN =
	BEGIN
		Object.Error("Magnitude Equal: Subclass must implement."); 
	END Equal;

Because of the requirement that the parameter of equal be in the same class as the receiver it is generally possible to compare magnitudes only if they are in the same class or if one of the objects is in a subclass of the class of the other. In this case send the equal message to the object in the superclass, using the other object as the parameter.

The method greater can be implemented in terms of the required less and so it is done as an aid to subclasses.


	PROCEDURE Greater(self: T; m:T):BOOLEAN =
	BEGIN
		RETURN m.less(self);
	END Greater;

The others are similar. For example:


	PROCEDURE Between(self: T; first:T; second:T):BOOLEAN =
	BEGIN
		RETURN self.greaterEqual(first) AND self.lessEqual(second);
	END Between;

The typical use of these methods is in if and while statements as in


	IF anExpression.less(aLeftHandSide) THEN
 		...
	END;
which assumes that anExpression and aLeftHandSide are both reference to objects in some subclass of Magnitude.T.

Importantly, note that objects are compared with less and with lessEqual, not with < or <=, which work only on a restricted subset of Modula-3's built in data. These functional methods extend the idea of comparison to a large class of objects. We can compare not only integers, characters, etc., but also fractions, geometric figures, and whatever else we choose to make a subclass of Magnitude.T.

3.6 Another simple example -- Fractions

In this section we will give an implementation of the magnitude subclass Fraction.T. Fractions are magnitudes as they can be compared. Actually they are more. They are a member of an important set of subclasses of Magnitude.T called Numeric classes, as they can also be operated on with the usual arithmetic operators. We could implement the following numeric class, but we won't. If we did then Fraction.T would be a subclass of Numeric.


TYPE Numeric = Magnitude.T OBJECT
	METHODS
		add (b: Numeric);
			(* Return the "sum" of self and b.  Meaning depends on class of self. *)
		subtract(b: Numeric);
		multiply(b: Numeric);
		divide(b: Numeric); 
	END;

This class won't really exist except in our documentation as its use as an abstract class requires that too many variable narrowings would need to be given in the concrete subclasses of it. Since it doesn't exist we call it a Pseudo Class. Since it is a pseudo class, Fraction.T actually subclasses Magnitude.T


Pseudo Class

	A purely conceptual class used to convey details of a protocol.  Not 
	actually declared or implemented.  Recognized by its complete 
	absence, except in documentation.  

The public interface for fractions is given below. Once again we see that only the new public methods are given here as well as a generator function.


INTERFACE Fraction;
IMPORT Object, Magnitude;

TYPE	
	T <: Public;
	Public = Magnitude.T OBJECT
		(* INVARIANT:	Denominator is always positive. 
				Fraction is always lowest terms. *)
		METHODS
			add(f: T):T;
			subtract(f: T):T;
			multiply(f: T):T;
			divide(f: T):T;
		END;

	PROCEDURE New(n,d: INTEGER):T; (* Return 0 if d=0 *)

END Fraction.

Since we wish to examine the implementation of this class we must first examine the private details from the interface PRFraction.i3.


INTERFACE PRFraction;
IMPORT Fraction, Magnitude, Object;

REVEAL
	Fraction.T = Fraction.Public BRANDED OBJECT
			fNumerator, fDenominator: INTEGER;
		METHODS
			initFraction(n,d: INTEGER): Fraction.T := InitFraction;

		OVERRIDES
			less		:= Less;
			equal		:= Equal;
			hash		:= Hash;
			add		:= Add;
			subtract	:= Subtract;
			multiply	:= Multiply;
			divide		:= Divide;
			writeIt		:= WriteFraction;
			copyFields	:= CopyFractionFields;
		END;

	PROCEDURE InitFraction(self: Fraction.T; n,d: INTEGER): Fraction.T ;
	PROCEDURE Less(self: Fraction.T; m: Magnitude.T):BOOLEAN ;
	PROCEDURE Equal(self: Fraction.T; o:Object.T):BOOLEAN ;
	PROCEDURE Hash(self: Fraction.T):CARDINAL;
	PROCEDURE Add(self: Fraction.T; f: Fraction.T):Fraction.T;
	PROCEDURE Subtract(self: Fraction.T; f: Fraction.T):Fraction.T;
	PROCEDURE Multiply(self: Fraction.T; f: Fraction.T):Fraction.T;
	PROCEDURE Divide(self: Fraction.T; f: Fraction.T):Fraction.T;
	PROCEDURE WriteFraction(self: Fraction.T; Wr.T) ; 
	PROCEDURE CopyFractionFields(self: Fraction.T; o: Object.T) ;

END PRFraction.

Note that we include the required overrides for Magnitude.T, and a consistent reworking (but not inheriting) of Numeric. Modula-3's strong typing won't let us redefine less as:


	less(m: Fraction.T);

because it doesn't permit redefinitions of the types of parameters in overridden methods when we create a new version in a subclass. This would be unsafe in any case. Therefore we include the restriction as a precondition that must be observed by clients. There is no system enforcement of this requirement, other than that not observing it could cause the system to crash. Actually we shall program defensively, and make less safe to use in case clients try to compare fractions to other magnitudes.

We have also included a class invariant as part of the documentation of the class. An invariant is a statement which can be guaranteed to be always true. In this case it means that to an external observer (client or subclass), we always have the numerator and denominator in lowest terms: any common factors will have been divided out of both. There will be short periods of time, while methods of this class execute that the invariant will not be true, but all methods will restore the truth of the statement before they exit, or else they must fail.

Class Invariant

A statement guaranteed by the implementor to be always true when observed from outside the class.

The implementation of this class depends on the following three support procedures, which are defined entirely within the implementation file Fraction.m3.


	PROCEDURE Sgn (a: INTEGER): INTEGER = 
	BEGIN
		IF		a > 0 THEN	RETURN 1;
		ELSIF	a = 0 THEN	RETURN 0;
		ELSE (* a < 0 *)	RETURN -1;
		END;
	END Sgn;

	PROCEDURE GCD(a,b: INTEGER): INTEGER =
	(*	Precondition: b > 0, a >= 0
		Postcondition: Returns the gcd of a and b *)
	VAR r: INTEGER;
	BEGIN
		IF a # 0 THEN
			REPEAT
				IF a < b THEN r := a; a := b; b := r; END;
				a := a MOD b
			UNTIL a <= 0;
		END;
		RETURN b
	END GCD;

	PROCEDURE Reduce (f: T) =
	(*  Precondition: denominator # 0
		Postcondition: f is in lowest terms *)
	VAR r: INTEGER;
	BEGIN
	r := GCD(ABS(f.fNumerator), ABS(f.fDenominator));
	f.fNumerator := f.fNumerator DIV r;
	f.fDenominator := f.fDenominator DIV r;
	IF f.fDenominator < 0 THEN
		f.fNumerator := - f.fNumerator;
		f.fDenominator := - f.fDenominator;
	END;
	END Reduce;

Gcd and Reduce both list preconditions, which any user (in this case just routines in this implementation part) must guarantee before calling the procedure, and postconditions, which the code itself will guarantee, if the precondition is true on entry. This listing of pre and postconditions is entirely within the spirit of programming by implementing abstractions, and forms a guide to the programming.

Precondition

A fact that the user of a method or procedure must guarantee before calling the method or function. This is the user's responsibility.



Postcondition

A fact that the implementor will guarantee will be true after the routine finishes if all of its preconditions were true at the time of call.

Reduce is a very special function for this module. Notice that it makes reference to the actual fields of its parameter f. Normally we like to avoid this. This must be done sparingly to avoid serious problems in our software library. Every reference to the implementation variables of an object means another procedure (such as Reduce) that must be rebuilt whenever the implementation of the class whose variables it uses must change. We strive for a high degree of independence between the pieces of our library and this usage can get in the way of this.

Initialization is the key method in this class. Note that it implements the class invariant and respects the precondition of reduce which it calls.


	PROCEDURE InitFraction(self: T; n,d: INTEGER): T =
	BEGIN
		IF d #0 THEN
			self.fNumerator := n;
			self.fDenominator := d;
			Reduce(self);
		ELSE
			self.fNumerator := 0;
			self.fDenominator := 1;
		END;
		RETURN self;
	END InitFraction;

The class generator function Fraction.new is entirely typical. All generator functions look like this, differing only in the type of the result local variable, and the initialization method called.


	PROCEDURE New(n,d: INTEGER): T =
	VAR result: T;
	BEGIN
		result := NEW(T);
		Object.FailNil(result);
		EVAL result.initFraction(n,d)
		RETURN result;
	END New;

The other methods are similar to add, using initialization to do the hard work:


	PROCEDURE Add(self: T; f: T):T =
	VAR result: T;
	BEGIN
		result := New(self.fNumerator * f.fDenominator + 
				self.fDenominator * f.fNumerator,
				self.fDenominator * f.fDenominator);
		Reduce(result);
		RETURN result;
	END Add;

WriteIt will produce a formatted fraction, putting angle parentheses around the number.


	PROCEDURE WriteFraction(self: T; f: Wr.T) = 
	BEGIN
		Wr.PutChar(f, '<');
		Wr.PutText(f, Fmt.Int(self.fNumerator));
		Wr.PutChar(f, ',');
		Wr.PutText(f, Fmt.Int(self.fDenominator));
		Wr.PutChar(f, '>');
	END WriteFraction;

Note that methods like add, as shown above are likely to overflow because of the extensive arithmetic done. This is true even when the result, reduced to lowest terms, is easily represented. One may use more sophisticated methods to help avoid this. A good (but not perfect) version of equal is shown below.


	PROCEDURE Equal(self: T; o:Object.T):BOOLEAN  =
	VAR r, n, p: INTEGER;
	BEGIN
		IF ISTYPE(o, T) THEN WITH m = NARROW(o,T) DO
			r := GCD(m.fDenominator, self.fDenominator);
			p := self.fDenominator DIV r;
			n := m.fDenominator DIV r;
			RETURN self.fNumerator * n = p * m.fNumerator;
			END; (* with *)
		ELSE
			RETURN FALSE;
		END;
	END Equal;

If overflow were not an issue (it is, of course) we could just compare the "cross products" of the numerator of each fraction and the denominator of the other. Note that we must narrow the parameter m, as it is required to be declared in type Object.T, while we need to assure that it is a Fraction.T.

Since Fraction.T has new instance variables, we must also override copyFields. The standard template for this method first copies all inherited fields using the inherited copyFields method and then individually copies the new fields from the parameter object (which must be narrowed) to itself. The only purpose of this is to make shallowClone work properly.


	PROCEDURE CopyFractionFields(self: T; o: Object.T) =
	BEGIN
		IF ISTYPE (o,T) THEN WITH temp = NARROW(o, T) DO
			Magnitude.T.copyFields(self, temp);
			(* Copy all new fields of temp into fields of self *)
			self.fNumerator := temp.fNumerator;
			self.fDenominator := temp.fDenominator;
			END;
		ELSE 
			Object.Error ("Copy Fraction Error");
		END;
	END CopyFractionFields; (* Override whenever you add new fields to an heir. *)

Note how we call the inherited copyfields method from within the new one: Magnitude.T.copyfields(self,temp). The call is not as a method at all, but as an ordinary procedure. This "direct" call guarantees that a specific procedure is called, rather than a polymorphic one. Once we have finished this class definition, we can do simple arithmetic on fractions. In fact, if we had a calculator program that correctly worked on integers it would be a matter of a few minutes work to turn it into a fraction calculator. This is because integers as objects (the class is Integers.T) have nearly the same protocol as this class. The calculator program would work by creating and manipulating integers through the Integers.T protocol. We need only change a few type declarations, and call the fraction generation function instead of the integer generator as appropriate to make the transformation. This is the power of programming with abstraction.

3.7 Summary

Classes may be concrete or abstract. An abstract class has some methods unimplemented. A class that is not abstract is a concrete class. Abstract classes are intended to form the common parent of other classes that need to have the same interface.

Classes describe their objects. They describe the features of each object within the class, which may be thought of as a set of its objects. The class features are instance variables and methods.

All methods obey the fundamental Dynamic Binding Principle, which guarantees that objects are permitted to act autonomously. Each object will determine precisely how it will interpret any message sent to it. It will determine this in accordance with its own personal type (class): the class it was given when it was created by the NEW operator.

This text describes a specific class structure, designed to illustrate data abstraction techniques and object oriented style of programming. The class structure emphasizes flexibility whenever possible.

The class Object.T describes basic functionality of all objects considered in this text. This functionality consists of a mechanism for determining the actual type of an object at run time, a facility for cloning an object and a means of writing a representation of the object on standard output.

The principle of inheritance implies that all objects in this system will be able to use and extend this basic functionality.

The class Collection.T, which inherits from Object.T, is the root of a large sub-hierarchy of classes, all of which provide a data collection service to their clients. Subclasses of Collection.T will handle lists, stacks, queues, and other specialized storage mechanisms. Collection.T provides insert and remove as well as an associated iteration mechanism.

Magnitude.T provides a means of creating classes with a partial order relationship between objects, by creating a comparison interface (less, greater, ...). We will create magnitude classes such as String.T.

Exercises

1. (3.2) Build and test the finite stack unit discussed in section 3.2. To test it you may push and pop any objects from any classes derived from Object.T. Characters are a good choice. To use characters, you must import the interface Character.i3 into your main program file. Character objects may be generated with the generator funciton asCharacter(char).

2. (3.2) Build and test an implementation of stacks that is like FiniteStack.T but which uses pointers to nodes which contain references to objects, to implement the storage for a stack that need not be finite.

3. (3.4) Give the public and private interface files that define a class FiniteStack.T which is a subclass of Collection.T. Be sure to include all methods required for stacks, all methods which are inherited and must be overridden, and an associated class FiniteStack.Iterator with all of its required parts. Give definitions of all of the methods used. For example what is the meaning of FiniteStack.T insert? Also be certain to define the appropriate generator function.

4. (3.4) Apply the dynamic binding principle to the following situation. Suppose you have built a "graphical figure" class that includes a method "draw." Its implementation is to call another method "vertices" that returns a list of the vertices (points) of the smallest rectangle with horizontal and vertical sides containing the figure. Draw then proceeds to draw lines from each vertex to the next in the returned list, finishing by drawing a line from the last vertex to the first. Thus, draw always draws a rectangle: the boundary rectangle of a figure. Next, suppose you build a new subclass of "graphical figure" called "five point star" You give it a new vertices method which returns the 10 points in order representing the five outer and the five inner intersections of the edges of a five point star. You do not implement a new draw method.

Now, suppose that you create a five point star and send it a draw message. Which version of vertices will be called by draw? The one that was originally created in the same class in which the draw method was created, or the one created in the new class that inherits the draw method? What will be drawn?

5. (3.4) a) How does the dynamic binding principle increase the flexibility of an implementor of a class?

b) The dynamic binding principle must be wisely used so that software becomes easier to build and use rather than harder. In light of this, what must implementors of override methods consider and what must they do?

c) Discuss the importance of stating preconditions and postconditions. Discuss the importance of adhering to preconditions and postconditions when we program.

6. (3.4) Explain the relationship between methods, the procedures that implement them, and the dynamic binding principle.

7.

8. (3.5) Implement the methods of class Magnitude.T except for hash, equal and less. Use these last two methods in your implementations.

9. (3.6) When we have a class B that "isLike" another class A rather than "isA" A we prefer not to implement B as a subclass of A but, perhaps, to implement B using an instance variable whose type is A. This is true even when we must write more code to do it this way. Why is this? To make the question more concrete, consider that a "set" isLike a "list" since they are both storage classes (collections) and that anything that we want to do with sets we can do with lists. Why don't we want to implement sets as a subclass of lists, but (perhaps) to implement sets using an instance variable that is a list?

10. (3.6) a) Implement FiniteStack.T from this Chapter.

b) Now build Fraction.T as discussed in this chapter.

c) Create some Fraction.Ts and one FiniteStack.T and push the fractions onto your stack.

d) Write out the stack using writeIt.

11. Build and test the following. It uses class Stack.T that is discussed in Chapters 4 and 7. We create a class Expression of data objects to insert into a stack.


MODULE Main;
IMPORT Object, Collection, Stack, Wr, Fmt, Stdio;

TYPE 
	Expression = Object.T OBJECT
			fLeft, fRight: INTEGER;
		METHODS
			initExpression (a,b: INTEGER): Expression	:= InitExpression;
			simplify()								:= Simplify;
		OVERRIDES
			writeIt									 := WriteIt;
	END;

VAR
	s: Stack.T;
	IT: Collection.Iterator;
	o: REFANY;
	e1,e2,e3: Expression;

PROCEDURE InitExpression(self: Expression; a,b: INTEGER): Expression =
	BEGIN
		self.fLeft := a;
		self.fRight := b;
		RETURN self;
	END InitExpression;

PROCEDURE WriteIt(self: Expression; f: Wr.T) =
	BEGIN
		IF fLeft = 0 THEN Wr.PutText(f, Fmt.Int(self.fRight));
		ELSIF self.fRight = 0 THEN Wr.PutText(f, Fmt.Int(self.fLeft));
		ELSE
			Wr.PutText(f, Fmt.Int(self.fLeft));
			Wr.PutChar(f, '+');
			Wr.PutText(f, Fmt.Int(self.fRight));
		END;
	END WriteIt;

PROCEDURE Simplify(self: Expression) =
		self.fLeft := self.fLeft + self.fRight;
		self.fRight := 0;
	END Simplify;

PROCEDURE NewExpression(a := 0; b := 0) =
	VAR result: Expression;
	BEGIN
		result := NEW(Expression);
		Object.FailNil(result);
		EVAL result.initExpression(a,b);
		RETURN result;
	END NewExpression;

BEGIN
	e1 := NewExpression (1,2);
	e2 := NewExpression (3,4);
	e3 := NewExpression ();
	s := NewStack();
	s.push(e1);
	s.push(e2);
	s.push(e3);
	s.writeIt(Stdio.stdout);
	Wr.PutChar(Stdio.stdout, '\n');

	IT := s.newIterator();
		(* Simplify all expressions in s. *)
	WHILE IT.nextItem(o) DO
		NARROW(o, Expression).simplify();
	END;
	s.writeIt(Stdio.stdout);
END Main.

12. Build and test the following modification of the code in Exercise 11. It derives the class SExpression from a simpler class STerm. It also pushes three of these objects onto a stack and manipulates them while they are in the stack. Be sure you understand which simplify and writeIt methods are being called. You might want to output the name of the class whenever a simplify or writeIt method is called. You can put a line like


	cout << "STerm::simplify";

into the simplify method of STerm and a similar one into that of SExpression.


MODULE Main;
IMPORT Object, Collection, Stack, Wr, Fmt, Stdio;

TYPE 
	Term = Object.T OBJECT
			fLeft: INTEGER;
		METHODS
			initTerm (a: INTEGER): Expression	:= InitTerm;
			simplify()							:= SimplifyTerm;
		OVERRIDES
			writeIt								 := WriteTerm;
	END;

	Expression = Term OBJECT
			fRight: INTEGER;
		METHODS
			initExpression (a: INTEGER): Expression	:= InitExpression;
			simplify()								:= SimplifyExpression;
		OVERRIDES
			writeIt									 := WriteExpression;
	END;

VAR
	s: Stack.T;
	IT: Collection.Iterator;
	o: REFANY;
	e1,e2,e3: Term;  (* Note the change of types from Exrecise 3.11 *)

(*   Term  **********************************************)

PROCEDURE InitTerm(self: Expression; a,b: INTEGER): Expression =
	BEGIN
		self.fLeft := a;
		RETURN self;
	END InitExpression;

PROCEDURE WriteTerm(self: Expression; f: Wr.T) =
	BEGIN
		Wr.PutText(f, Fmt.Int(self.fLeft));
	END WriteTerm;

PROCEDURE SimplifyTerm(self: Expression) =
	(* Nothing. *)
	END SimplifyTerm;

PROCEDURE NewTerm(a := 0) =
	VAR result: Term;
	BEGIN
		result := NEW(Term);
		Object.FailNil(result);
		EVAL result.initTerm(a);
		RETURN result;
	END NewTerm;

(*   Expression  **********************************************)

PROCEDURE InitExpression(self: Expression; a,b: INTEGER): Expression =
	BEGIN
		EVAL self.initTerm(a);
		self.fRight := b;
		RETURN self;
	END InitExpression;

PROCEDURE WriteExpression(self: Expression; f: Wr.T) =
	BEGIN
		IF fLeft = 0 THEN Wr.PutText(f, Fmt.Int(self.fRight));
		ELSIF self.fRight = 0 THEN Wr.PutText(f, Fmt.Int(self.fLeft));
		ELSE
			Wr.PutText(f, Fmt.Int(self.fLeft));
			Wr.PutChar(f, '+');
			Wr.PutText(f, Fmt.Int(self.fRight));
		END;
	END WriteExpression;

PROCEDURE SimplifyExpression(self: Expression) =
		self.fLeft := self.fLeft + self.fRight;
		self.fRight := 0;
	END SimplifyExpression;

PROCEDURE NewExpression(a := 0; b := 0) =
	VAR result: Expression;
	BEGIN
		result := NEW(Expression);
		Object.FailNil(result);
		EVAL result.initExpression(a,b);
		RETURN result;
	END NewExpression;

BEGIN
	e1 := NewExpression (1,2);
	e2 := NewExpression (3,4);
	e3 := NewTerm (6);
	s := NewStack();
	s.push(e1);
	s.push(e2);
	s.push(e3);
	s.writeIt(Stdio.stdout);
	Wr.PutChar(Stdio.stdout, '\n');

	IT := s.newIterator();
		(* Simplify all expressions in s. *)
	WHILE IT.nextItem(o) DO
		NARROW(o, Expression).simplify();
	END;
	s.writeIt(Stdio.stdout);
	Wr.PutChar(Stdio.stdout, '\n');

	IT.reset();
		(* Write the stack by iterating over its contents. *)
	WHILE IT.nextItem(o) DO
		NARROW(o, Expression).writeIt(Stdio.stdout);
		Wr.PutChar(Stdio.stdout, '\n');
	END;

END Main.

13. This exercise is a real challenge at this point. It will be very easy if done later. The finite stack discussed in this chapter can't be used like the stack in Exercises 11 and 12 unless we build an iterator class to go with it. If that is done, then FiniteStack.T could be re-built so that it inherits from Collection.T, which is more appropriate. A stack iterator should work from the top of the stack toward the bottom. It needs a nextItem method that will yield each item once and return FALSE thereafter. Implement this class and make the corresponding changes to FiniteStack.T. Then re-do Exercise 12 with this new stack type in place of Stack.T. Hint: For an internal state variable, a FiniteStack.Iterator needs only an integer to keep track of the next item that has not yet been yielded. Initially this should be the index of the top of the stack.