When classifying things we first group them either very generally, e.g. as animal, vegetable or mineral, or very specifically, e.g. as bees or roses, depending on circumstances. These approaches correspond to the programming techniques known as "top down" and "bottom up" design, respectively.
In practice, it is fairly easy to classify things in general terms, but appearances can be deceptive when it comes to detail. Things which look alike may actually have very different origins. Thus the hedgehog and the spiny anteater look remarkably similar and live very similar lives, yet, genetically, they are not closely related at all.
SIMULA takes the top down approach as the safest, just as natural science has tended to. It allows us to define a CLASS, as we have seen, to represent a general type of object. This may then be extended, to reflect the special characteristics of sub-types by defining sub-classes of the original. These retain some or all of the characteristics of the parent type, but include characteristics which are only found in certain objects of this type.
It is important to notice that sub-types in SIMULA extend and refine the range of characteristics of the parent type. The more general the class of objects described, the fewer characteristics that are given to it.
One example of the use of such sub-types, that we have already seen, is the Class File and its sub-classes. If you look back at chapter 7, you will see that special purpose files are represented by adding to the attributes of first File, then ImageFile and, for PrintFile, Outfile. We can show this as a tree; a family tree. Diagram 11.1 is the File family tree as we have seen it so far.
Diagram 11.1 - Family tree of class File
class File
|
------------------------
|
File class ImageFile
|
-----------------------------------------------------------
| | |
ImageFile class Infile ImageFile class DirectFile ImageFile class OutFile
|
Outfile class PrintFile
The syntax of a sub-class declaration is very simple. The keyword
class is
preceded by the name of the parent class. Otherwise the declaration is the
same as for a simple class. The new class is said to be "prefixed" by the
parent.
Each Page will contain printed blocks. The first Page in each Chapter will have a title block, followed by a sequence of other blocks. The other blocks could contain text or diagrams. A linked list of Print_Block objects can represent the contents of a Page.
Example 11.1 shows the outline of such a set of classes. The parent class for blocks on a page is Print_Block. It contains a link in the form of ref(Print_Block) Next. It also contains an array of text variables, Contents, representing the lines in the block. Parameters Width and Length are integers representing line length and number of lines in the block, respectively. Each member of the text array is filled with blanks, initially.
Example 11.1: Representing pages as Print_Block objects.
begin
class Page;
begin
class Print_Block(Width,Length);integer Width, Length;
begin
ref(Print_Block)Next;
text array Contents(1:Length);
integer Count;
for Count:=1 step 1 until Length do Contents(Count):-Blanks(Width)
end--of--Print_Block;
Print_Block class Title_Block(Title);text Title;
begin
Contents(Length//2):=Title
end--of--Title_Block;
Print_Block class Text_Block;
begin
for Count:=1 step 1 until Length do
begin
InImage;
Contents(Count):=Intext(Width)
end
end--of--Text_Block;
Print_Block class Diagram(Title); text Title;
begin
Contents(1):=Title
end--of--Diagram;
ref(Print_Block) Head, Tail, New_Block;
text Directive;
integer Len;
procedure Add(NewBlock); ref(Print_Block) NewBlock;
begin
if Head==None then Head :- NewBlock;
if Tail=/=None then Tail.Next :- NewBlock;
Tail :- NewBlock
end++of++Add;
Directive :- InText(2);
while Directive NE "$E" do
begin
if Directive= "$B" then
begin
Len := InInt;
InImage;
Add(new Title_Block(80,Len,InText(80)))
end
else if Directive="$C" then
Add(new Text_Block(80,InInt))
else begin
Len := InInt;
InImage;
Add(new Diagram(80,Len,InText(80)))
end;
Directive :- InText(2)
end
end..of..Page;
new Page;
end
This parent class is used to prefix the three classes representing different
types of block on a page. Each contains additional attributes, actions or
both. Two contain parameters. Note that these also are additional to
those in the parent class.
The directive "$B" means that a Title_Block is required. The number of, mostly blank, lines to be used, is given as an integer following the $B directive. The text for the title parameter is on the following line. The title is copied into the middle line of the block by the actions of a new Title_Block.
The directive "$C" means that a new Text_Block is required. The number of eighty character lines in this block is given as an integer following the directive and is used as a dynamic upper bound to Contents. The contents of the block are on the following lines. The actions of a new Text_Block copy this into Contents.
The directive "$D" means that a space for a diagram should be left. Again the number of lines to be left follows and the next line is the text to be used for the title of the diagram. This is copied into the first text elememt of Contents by the actions of a new Diagram object.
11.2 By using a linked list to hold lines, remove the need to specify the number of lines in a Text_Block. Use a line breaker to avoid splitting words at the ends of a line.
Example 11.2: Concatenation .. sub-sub-classes
begin
class OuterMost(First); text First;
begin
text OuterText;
OuterText :- Copy("Outermost=first on prefix chain");
OutText(OuterText);
OutImage;
OutText(First);
OutImage
end--of--OuterMost;
OuterMost class Middle(Second); text Second;
begin
text MiddleText;
MiddleText :- Copy("Middle=second on prefix chain");
OutText(MiddleText);
OutImage;
OutText(First);
OutText(Second);
OutImage
end--of--Middle;
Middle class InnerMost(Third); text Third;
begin
text InnerText;
InnerText :- Copy("Inner=last on prefix chain");
OutText(InnerText);
Outimage;
OutText(First);
OutText(Second);
OutText(Third);
OutImage
end--of--InnerMost;
new OuterMost("One");
new Middle("One","Two");
new InnerMost("One","Two","Three")
end**of**program
Example 11.2 shows this in a trivial example. Try compiling and running it to see the effects for yourself.
There is no limit to the number of levels of such prefixing which you may use. The same rules apply with each extension. The combining of attributes and actions in the correct order is called the "concatenation" of the prefixing classes and the final extension.
The sequence class, sub-class, sub-sub-class etc. is called a "prefix chain".
In more technical descriptions the first class on the prefix chain is called the "outermost" and the final one the "innermost". A sub-class is "inner" to its parent, grandparent etc. A class is "outer" to its sub-classes, their sub-classes etc.
In a parent class the use of the identifier always refers to the declaration of that identifier inside that class.
In a sub-class the use of the identifier always refers to the declaration of that identifier inside the sub-class.
That is fairly straightforward. The formal description is that the use of an identifier within the class body where a declaration exists for it, always refers to the location associated with that declaration. If no declaration exists for it inside the class body where it is used, it is said to be "uncommitted" at that prefix level. It is then assumed to refer to the innermost prefixing class containing such a declaration. If there is no class on the prefix chain outer to the class in which the identifier is used which contains a declaration for it, the identifier is looked for outside the class and is not an attribute of it.
Example 11.3: Name conflicts in prefix chains.
begin
class Grandad;
begin
text T;
T :- Copy("Grandad"); ! Always refers to the preceding declaration;
OutText(T); ! Should always print Grandad;
OutImage
end--of--Grandad;
Grandad class No1Son;
begin
text T; ! Conflicts with declaration in Grandad;
T :- Copy("No1Son"); ! Refers to T immediately above;
OutText(T); ! Should always print No1Son;
OutImage
end--of--No1Son;
Grandad class No2Son;
begin
OutText(T); ! Should always print Grandad, no conflict;
OutImage
end--of--No2Son;
No1Son class GrandDaughter;
begin
OutText(T); ! Should use T from No1Son, print No1Son;
OutImage
end--of--GrandDaughter;
new Grandad; ! Prints Grandad;
new No1Son; ! Prints Grandad
No1Son;
new No2Son; ! Prints Grandad
Grandad;
new GrandDaughter; ! Prints Grandad
No1Son
No1Son;
end**of**program
Example 11.4: The use of inner.
begin
class Parent;
begin
OutText("Before inner");
OutImage;
Inner;
OutText("After inner");
OutImage
end--of--Parent;
Parent class Child;
begin
OutText("In the inner class");
OutImage
end--of--Child;
new Parent;
new Child
end**of**program
The usefulness of sub-class actions is increased still further by the inner statement. This allows a parent class to specify one set of actions to be performed before those of its sub-classes and another to be performed after them. Consider example 11.4.
The class Parent contains a statement consisting of the keyword inner and nothing else. When an object is generated from Parent itself this statement is ignored.
The class Child is prefixed by Parent. If the inner statement was not in Parent all the actions of Parent would be performed before any actions of Child, whenever an object was generated from Child. The effect of the inner statement is to alter this sequence.
The two statements of Parent before the inner are executed first. The actions of sub-class Child are performed when the inner is reached. The remaining statements of Parent are only performed when those of Child are complete.
This allows the outer class to define both a prologue and an epilogue to the actions of the inner one. When an object of such an outer class is generated rather than of the inner class, the inner statement is ignored.
Only one inner statement can appear in a class body, since it can only have one inner class whose statements it will perform.
A sub-class may contain an inner statement, in which the actions of any sub-sub-classes will be performed when it is reached, and so on along the prefix chain. Practical uses of this feature, especially in conjunction with prefixed blocks, will be demonstrated later in this book.
11.4 Study the following program. What would its output be? Try running it to check your answer.
begin
class Outer;
begin
text T;
T :- Copy("ABC");
OutText(T);
inner;
OutImage
end--of--Outer;
Outer class Middle;
begin
text T;
T :- Copy("DEF");
OutText(T);
inner;
OutText(T);
OutImage
end--of--Middle;
Outer class Centre;
begin
text T1;
T1 :- Copy("DEF");
OutText(T);
OutImage
end--of--Centre;
Middle class Inner;
begin
text T1;
T1 :- Copy("GHI");
OutText(T);
T := T1;
end--of--Inner;
new Outer;
new Middle;
new Centre;
new Inner
end**of**program
When a class object is generated it posesses all the attributes of the class whose name is given in the object generator. This includes any visible attributes from classes on its prefix chain, following the rules given above concerning name clashes.
The type of such an object is the class specified and this is called its qualification. It can also be thought of as being qualified by the classes on its prefix chain, except that not all the attributes of these may be visible.
A variable which is declared as a ref to a class which is the qualification of an object or is on the prefix chain of its qualifying class may be used to access that object. The type of the reference variable used controls how much of the prefix chain may be so accessed.
It is only legal to treat an object which is being remotely accessed as if it was qualified by the class of the referencing variable. Thus, in example 11.1, when New_Block is set to denote a new Text_Block only the attributes declared in Print_Block, which prefixes Text_Block, may be accessed through New_Block. This is because New_Block is declared as a ref(Print_Block) not a ref(Text_Block) variable. This means that the statement
NewBlock.Count := 3
would be illegal.
Breaking any of these rules will cause the SIMULA system to report an error, either during compilation or at runtime. Some extra features of SIMULA can be used to circumvent these restrictions, as we shall see later, but these checks provide an important safeguard against serious errors which could occur otherwise.
The operator is checks whether an object is qualified by a particular class. It will only give the value True when the object's innermost class matches that with which it is compared. Thus
Obj is ThisClass
will only give True if the object referenced by Obj was generated by
new ThisClass.
The operator in makes a less strict check. It will give True if the object is either of
the specified class or has that class on its prefix chain.
The use of is and in is shown in example 11.5.
Example 11.5: Use of is and in.
begin
class A;
begin
integer I;
end--of--A;
A class A1;
begin
integer K;
end--of--A1;
A class A2;
begin
integer L;
end--of--A2;
ref(A) Obj1,Obj2,Obj3,Obj4;
Obj1 :- new A;
Obj2 :- new A1;
Obj3 :- new A2;
for Obj4 :- Obj1,Obj2,Obj3 do
begin
if Obj4 is A then OutText("Object is A") else
if Obj4 in A then
begin
OutText("Object in A");
if Obj4 is A1 then OutText(" and is A1")
else OutText(" and is A2")
end else OutText("Object is not A and is not in A");
OutImage
end
end**of**program
A comparison is more formally called a "simple Boolean expression", giving the value True or False. The keywords True and False are themselves simple Boolean expressions, with a constant value. Boolean variables and Boolean procedures, like LastItem in a text, are also simple Boolean expressions.
Thus a Boolean expression is a sequence of SIMULA which can be evaluated to True or False.
3 = 2
has the value False, and so the expression
not 3 = 2
has the value true.
The expression
3 = 2 and 4 = 4
has the value False, since one of its sub-expressions has the value False.
The examples in 11.6 show the other possibilities.
Examples 11.6: The use of and.
a) 4=4 and 7=7; ! Value is True
b) 2=2 and 6=8; ! Value is False
c) 10=1 and 2<0; ! Value is False
Examples 11.7: The use of or.
a) 3=2 or 4=4; ! Value is True
b) 4=4 or 7=7; ! Value is True
c) 2=2 or 6=8; ! Value is True
d) 10=1 or 2<0; ! Value is False
Examples 11.8: The use of eqv
a) 3=2 eqv 4=4; ! Value is False
b) 4=4 eqv 7=7; ! Value is True
c) 2=2 eqv 6=8; ! Value is False
d) 10=1 eqv 2<0; ! Value is True
Examples 11.9: The use of imp.
a) 3=2 imp 4=4; ! Value is True
b) 4=4 imp 7=7; ! Value is True
c) 2=2 imp 6=8; ! Value is False
d) 10=2 imp 2<0; ! Value is True
It is easy to confuse the operator and with the operator or in ordinary speech, since we often use these words very loosely. You should take great care to get your meanings precise in SIMULA.
The examples in 11.7 show the possible combinations.
The examples in 11.8 show the possible combinations.
imp is an abbreviation for "implies".
| B1 = | TRUE | TRUE | FALSE | FALSE |
| B2 = | TRUE | FALSE | TRUE | FALSE |
| B1 AND B2 = | TRUE | FALSE | FALSE | FALSE |
| B1 AND THEN B2 = | TRUE | FALSE | FALSE | FALSE |
| B1 OR B2 = | TRUE | TRUE | TRUE | FALSE |
| B1 OR ELSE B2 = | TRUE | TRUE | TRUE | FALSE |
| B1 EQV B2 = | TRUE | FALSE | TRUE | FALSE |
| B1 IMP B2 = | TRUE | FALSE | TRUE | TRUE |
| NOT B1 = | FALSE | FALSE | TRUE | TRUE |
| NOT B2 = | FALSE | TRUE | FALSE | TRUE |
Example 11.11 shows the effects of bracketing on parts of 11.10. Note that the innermost brackets' contents are evaluated first and so on.
Example 11.10: Evaluation of complex Boolean expressions.
3=2 or 7=7 and 9<10 eqv 7>4 imp not 23=23
1) False or True and True eqv True imp not True
2) False or True and True eqv True imp False
3) False or True eqv True imp False
4) True eqv True imp False
5) True eqv False
6) False
Example 11.11: Evaluation of complex Boolean expression with bracketing.
3=2 or (7=7 and (9<10 eqv 7>4)) imp not 23=23
1) False or (True and (True eqv True)) imp not 23=23
2) False or (True and (True)) imp not True
3) False or (True) imp not True
4) False or True imp False
5) True imp False
6) False
if I=J then
begin
if K>L then
can nearly always be replaced by
if (I=J) and (K>L) then
which seems a lot clearer, to me at least. (The reason for saying "nearly always"
will be explained below.)
The use of parentheses, even when, as above, they are not strictly necessary, can also make it easier to see which sub-expressions are linked by which operators. It is not always easy to remember which of and and or will be evaluated first, but it is easy to remember that bracketed expressions are evaluated before others. The expressions
I=J and K<L or I<L and K=J
and
(I=J and K<L) or (I<L and K=J)
have exactly the same value, but the second is much clearer.
Finally, a warning; do not try to be "clever" in your use of Boolean expressions. Keep them as straightforward and clear as possible. Sometimes it may be better to use two nested begin statements rather than a convoluted Boolean expression, keeping the program's meaning clear at the cost of making it slightly longer.
In addition to or and and, SIMULA has operators or else and and then. These produce the same value as their simpler counterparts, as table 11.1 shows. They are included to allow programs to be written which run faster, at the expense of introducing the possibility of unpredictable side effects from procedure calls. In most cases and then is interchangeable with and, while or else is interchangeable with or.
The difference is that when two sub-expressions are linked by and or by or, both are evaluated, regardless of the value of the first. Yet, with and, when the value of the first sub-expression is False, we know without having to evaluate the second that the value of the two anded together must be False. Similarly with or, if the first sub-expression gives True, the overall value will always be True. The evaluation of the second sub-expression is unnecessary in these cases.
The use of and then prevents the evaluation of the second sub-expression if the first is False. The use of or else prevents it if the first is True. This may make the program run faster. It also prevents any procedures which form part of the second sub-expression being called.
It is the second effect which can cause problems. If the calling of a type procedure in the second sub-expression affects values which are used elsewhere in the program, the effect of skipping its evaluation could be disastrous. If you must write such programs, you must use the simpler forms, even though they will result in slower running.
Example 11.12 shows a program with side effects. Running it and supplying first a sequence not containing 99 and then one which does contain it demonstrates the problem. It may seem "smart" to write like this. It is certainly an interesting technique. It is even more certainly a very unsafe one.
We have seen how to use classes as prefixes to form families of sub-classes, which extend their parent's attributes.
We have learned the rules governing this concatenation, including the order of execution of actions.
We have seen how the qualification of a class object and its prefix chain control both the objects which may be pointed to by references of particular class types and the attributes which may be accessed remotely using such references.
The use of inner to allow epilogues of actions in prefixing classes has been demonstrated.
The use of Boolean operators to extend the power of Boolean expressions has been shown, along with some dangers associated with their use.
Example 11.12: Unsafe side effects in a complex Boolean expression.
begin
integer I,J,K;
Boolean procedure SideSwipe;
begin
J := J + 1; ! J is updated here;
SideSwipe := I>0
end..of..SideSwipe;
K := InInt;
while J<K do
begin
I := InInt;
comment The first test could interfere with the updating of J;
if I NE 99 and then SideSwipe then OutText("Greater")
else OutText("Less")
end;
OutImage
end**of**program