Chapter 18 - Virtually Anything Is Possible

A More Formal Definition Of Scope; Virtual, Hidden and Protected

The scope of identifiers

It is a fundamental rule of SIMULA that an identifier cannot be used unless it has a matching declaration. The declaration defines the meaning of that identifier.

We have seen that a declaration made in an outer block is valid inside blocks which are enclosed by that block or which are prefixed by it, but not vice versa. This rule covers the use of identifiers declared in prefixing classes within subclasses.

Further we have seen that a declaration in an inner block will supercede that in an outer block where the names of the identifiers are the same. This is true even when the type of the declaration is the same in both outer and inner block. A different location is indicated for a variable, a different body for a procedure or a class.

This is a brief summary of the scope rules that we have used so far. They are very important and it is essential to grasp them fully.

In addition to these basic rules, we have seen that when and qua are provided and allow the scope rules for classes and subclasses to be bent. They allow a reference to a parent to be extended to access an object of a subclass or access to attributes of a class from a reference to a subclass in which their identifiers have been redeclared. There are three more features of SIMULA which concern themselves with relaxing the scope rules for classes. This chapter will examine these in some detail.

The concept of virtual quantities

One feature of SIMULA which is found in almost no other programming language is the virtual declaration. This allows a declaration to be made in a parent class for a label, switch or procedure without a matching definition or with a definition which will only be used if no redefinition is made in a subclass.

This reverses the normal rule about identifiers in inner blocks being inaccessible to outer blocks. As long as a virtual declaration is made in the prefixing class, any uses of that identifier in the body of the prefixing class will be assumed to refer to the declaration of that identifier within the body of the innermost class containing such a declaration.

This is rather simpler in practice than it sounds in theory. Let us use an example to show how it works in the simplest cases.

Example 18.1 shows a class Prefix containing two virtual declarations a procedure Banner and a label Dest. Of these, only Dest is matched by an actual label in the body of Prefix. Banner is unmatched.

Example 18.1: Virtual quantities.

   begin
   
      class Prefix;
         virtual: procedure Banner; label Dest;
      begin
         Banner;   ! Call a virtual procedure with no match here;
         goto Dest;
   Dest:     ! Default match for goto, skips nothing;
         OutText("Dest in Prefix used");
         OutImage
      end++of++Prefix;
      
      Prefix class Stars;
      begin
         procedure Banner;
         begin
            OutText("************************************");
            OutImage
         end--of--Banner;
         OutText("This should not be printed");
   Dest:   ! This will replace the Dest in Prefix;
         OutText("Dest in Stars used");
         OutImage
      end++of++Stars;

      Prefix class Dashes;
      begin
         procedure Banner;
         begin
            OutText("------------------------------------");
            OutImage
         end--of--Banner;
         OutText("This should be printed"); ! No local match for Dest;
         OutImage;
         OutText("Dest in Prefix used");
         OutImage
      end++of++Dashes;
      
      new Stars;
      new Dashes;
      
   end**of**program
Prefix is used as a parent class to both Stars and Dashes. Each of these contains a procedure called Banner, providing a match for the virtual procedure in their parent. Only Stars contains a label called Dest to match the virtual declaration in Prefix.

Prefix contains a call on Banner. Since it has no match for this itself, the statement

             new Prefix
would be reported as a runtime error.

The statements

             new Stars    or
             new Dashes
are perfectly legal, however. The procedure declared in the subclass is called from the parent's body, via the parent's virtual declaration, which is matched in the subclass.

Prefix also contains a go to statement, which uses label Dest as its designational expression or destination. Since there is a matching label in the body of Prefix, this would cause no problems if new Prefix were used. For new Dashes, the only match would be the label in Prefix itself and so the goto would lead to this, with no effect from the virtual declaration. For new Stars the existence of a declaration of label Dest inside the body of the subclass would cause the goto to jump to this rather than to the label in the parent. The scope rules are reversed.

Consider the example carefully and try to work out what its output would be like. Then, of course, you should run it to check.

What use is the virtual concept?

The virtual concept allows the writing of much more powerful classes, with the intention of using them as prefixes to specialised subclasses. This is particularly useful when we want to create packages of such parent classes.

The parent class can be written as a template. Its actions can be calls on virtual procedures and jumps to virtual labels. These can be left as blanks, to be filled in by the subclass, or given default matches in the parent class, which can be overidden by a subclass.

Take a practical example. We want to provide a class for use as a prefix and we want this class to write messages at the start of its actions and at the end. It also contains some procedures for writing headings, and starting paragraphs. It is a simple component in a package.

These are all candidates for virtual procedures. They provide clearly defined facilities, for which meaningful defaults can be given. They are all likely to need to be tailored to fit some purposes.

Formal description of the virtual concept

The list of virtual specifications for a class comes after any parameter type specifiers and immediately before the statement which makes up the class body.

The syntax is the keyword virtual, followed by a colon, followed by a list of type specifiers separated by semi-colons. These are of the same form as the type specifiers for parameters, but only the types label, switch, procedure and type procedure are allowed.

Each identifier specified as virtual is either matched by a normal declaration inside the class body which follows or unmatched. It is not an error to use an unmatched virtual identifier inside this class body, so long as this class is only used to prefix subclasses which contain a match.

When an object is created which has virtual quantities specified in one or more classes on its prefix chain, all these must be matched by declarations in those classes or in classes inner to them. Where matches exist at more than one equal or inner level, the innermost is used.

A final example of the virtual concept: The use of a virtual label.

The virtual procedure has some obviously beneficial uses, but what about the virtual label? Example 18.2 shows one very important case, which could not be dealt with adequately in almost any other way.

Example 18.2: Use of virtual label in a package.

   class SafeMths;
   begin
   
      class SafeDivide(Dividend,Divisor); real Dividend, Divisor;
         virtual: label ZeroAction,Perform;
      begin
         real Result;
         if Divisor=0 then go to ZeroAction;
         go to Perform;
   ZeroAction:
         Divisor := 0.0001;
         Inner;
   Perform:
         Result := Dividend/Divisor;
   Complete:
      end++of++SafeDivide;

      class SafeAdd(Val1,Val2); real Val1,Val2;
         virtual: label SafeLab;
      begin
         ! Various actions;
      end++of++SafeAdd;

      ! Various other maths classes;

   end--of--SafeMaths;
The class SafeMaths is a package of specially safeguarded arithmetic features. Most are shown only in outline, but SafeDivide will demonstrate the general idea and is given in full.

Class SafeDivide has two virtual labels ZeroAction and Perform defined. Before performing its division the divisor is checked. If it is zero, the division will result in a runtime error. (I am sorry to get mathematical again. I am sure we all remember that an attempt to divide by zero is illegal in normal arithmetic.) To allow the program to continue a goto is performed to ZeroAction when a zero divisor is found. If a non-zero divisor is found, a goto is carried out to the other virtual label, Perform, to avoid the zero actions. Here a normal real division is performed. This is followed by a non-virtual label at the end of the class, called Complete. Note that Complete is placed after the Inner statement, while ZeroActions and Perform are placed after it.

The instructions following ZeroAction provide a default sequence in the event of an attempt to divide by zero. The decision has been taken to replace zero with a suitably small decimal fraction and then divide. This removes the possibility of a runtime error, but may not give a suitable result. Example 18.3 shows a program using SafeMaths, where division is handled by a subclass of SafeDivide. The subclass has provided its own default action when a zero divisor is found.

Example 18.3: Use of package with virtual label, SafeMaths.

begin
      external class SafeMths;
      SafeMths
      begin

         SafeDivide class MyDivide;
         begin
   ZeroActions:   ! Override the default ZeroActions;
            Result := 9999999999999.9999999;
            go to Complete;
         end++of++MyDivide;

         OutFix(new MyDivide(6,0).Result,4,20);
         OutImage
      end..of..prefixed..block
   end**of**program
By providing its own match for ZeroAction the subclass can choose to substitute a very large value for the result of the division. This would probably represent the largest legal value for a real on the particular system. The value in the example is chosen at random.

A tighter specification

Up to date SIMULA systems will allow you to specify the type of any result and of any parameters to a virtual procedure in its virtual specification . Example 18.4 shows an example. Older systems will not support this.

Example 18.4: Fully specified virtual procedure.

   class Virtuous;
      virtual:procedure CharVal is character procedure CharVal(IntVal);
                                               integer IntVal;; 
   begin
      ! Declarations and actions;
   end--of--Virtuous;
Once such a virtual procedure is specified all matches must have exactly the same form.

Exercises

18.1 Rewite 18.2 to redefine the normal actions for a divide to be those of subtraction. (N.b. Perform.)

18.2 What would be the effect of removing the inner statement in example 18.2?

18.3 Rewrite example 18.2 using a virtual procedure to replace the virtual label.

Keeping it in the family

One property of the classes that we have written so far is that all declarations made in them have been accessible remotely, by inspection if not always by dot notation. Yet when considering SIMSET I indicated that some attributes of class Linkage were not accessible in user programs. This feature is known as attribute protection and can be used to stop any attempts to meddle with attributes which are designed for internal use only. The file references returned by calls on SysIn and SysOut are also protected from direct use by programs. Although the files they represent are attributes of Environment class BasicIO which prefixes the main program block, these can only be reached by calling type procedures.

SIMULA allows two levels of protection of attributes. The first protects the attribute from use outside of the body of the class, any subclasses or blocks prefixed by the class or its subclasses. This is achieved by specifying that attribute as protected. Example 18.5 shows the use of a protected specifier.

Example 18.5: Use of protected specifier.

   begin
   
      class Counter;
         protected Tally;
      begin
         integer Tally;
         procedure Increment; Tally := Tally + 1;
         procedure Decrement; Tally := Tally - 1;
         integer procedure Total; Total := Tally;
      end++of++Counter;
   
      Counter class DoubleCounter;
      begin
         procedure Increment; Tally := Tally + 2;
         procedure Decrement; Tally := Tally - 2;
      end++of++DoubleCounter;
      
      ref(Counter) Count1;
      ref(DoubleCounter) Count2;
      integer I;
      Count1 :- new Counter;
      Count2 :- new DoubleCounter;
      for I := 1 step 1 until 10 do
      begin
         Count1.Increment;
         Count2.Decrement 
      end..of..for;
      OutInt(Count1.Total,8);
      OutInt(Count2.Total,8);
      OutImage
      
   end**of**program
Notice that the form of a protected specifier is the same as a type specifier or a mode specifier, although it is only allowed for classes.

The example shows the integer Tally specified as protected. This means that it can be accessed directly inside class Counter and Counter class DoubleCounter, but only through calls on Increment, Decrement and Total from the main program. The central attribute is kept safe from outside manipulation.

This feature allows object oriented programming to become a much more powerful concept. A data structure can be defined which can only be manipulated in ways which are also defined in the same class declaration, as procedure attributes. Attempts at cheating by manipulating them directly will be prevented.

At the same time, the range of defined operations on the data can be extended, as can the data structure. This can be done by creating a subclass of the original definition.

The protected specification allows extensible but externally secure objects for use in programming. The second level of protection allows complete internal security as well.

An attribute must be specified as protected in the class where it is declared, not in an inner one.

Keeping it from the children

Once a class has been fully defined for a particular purpose it may be prudent to hide some of its attributes even from its own subclasses. This is the case with the references returned by Suc and Pred in Linkage class Link of SIMSET. Even in subclasses of Link they can only be accessed by procedure calls.

To achieve this an attribute must be specified as hidden as well as protected. It is not necessary to make an attribute hidden until some subclass of that in which it is declared and specified protected.

The hidden specifier has exactly the same form as the protected specifier, except that the keyword hidden replaces protected.

If an attribute has been specified as virtual at an outer level, specifying it as hidden at the current level means that no matches will be made at levels inner to the current one.

Example 18.6 shows the use of hidden. 18.6a is a package used to prefix 18.6b. The integer Tally is not directly accessible outside the package, since it is specified hidden in classes Count and Double Count. The integer CheckValue is only accessible inside the class BasicCount, which prefixes both Count and DoubleCount.

Example 18.6: Use of hidden and protected.

a) Package using both.
   
   class TallyPack;
   begin

      class BasicCounter;
      hidden protected CheckValue;
      begin
         integer CheckValue;
         integer procedure CVal; CVal := CheckValue;
         CheckValue := 1;
      end++of++BasicCounter;

      BasicCounter class Counter;
         protected Tally;
      begin
         integer Tally;
         procedure Increment; Tally := Tally + 1;
         procedure Decrement; Tally := Tally - 1;
         integer procedure Total; if Total<CVal then Total := CVal
                                                else Total := Tally;
      end++of++Counter;
   
      Counter class DoubleCounter;
      begin
         procedure Increment; Tally := Tally + 2;
         procedure Decrement; Tally := Tally - 2;
      end++of++DoubleCounter;
      
      end--of--TallyPack;

b) Program using 18.6a.

   begin
      external class TallyPack;
      TallyPack
      begin
         ref(Counter) Count1;
         ref(DoubleCounter) Count2;
         integer I;
         Count1 :- new Counter;
         Count2 :- new DoubleCounter;
         for I := 1 step 1 until 10 do
         begin
            Count1.Increment;
            Count2.Decrement 
         end..of..for;
         OutInt(Count1.Total,8);
         OutInt(Count2.Total,8);
         OutImage
      end
      
   end**of**program
You will notice that it is possible to specify an attribute both hidden and protected at once. This is done by using both keywords, hidden and protected, separated by at least one space, in the specifier. They may be used in either order.

The order of specifiers

We have now encountered all the specifiers in SIMULA. The complete list is mode, type, protection and virtual specifiers.

Procedures only use mode and type specifiers as appropriate. These were described in chapter 6. The mode specifiers always come before the type specifiers, when both are present.

Classes use type, protection and virtual specifiers. Type specifiers are the same as those for procedures. The others have been described in this chapter. The order, when present is type followed by protection followed by virtual specifiers.

Summary

We have revised the basic scope rules which define where an identifier may be used once it has been declared. We have also covered the effects of redeclaring an identifier in an inner block.

The concept of virtual specification has been introduced, allowing procedures, switches and labels to be used at prefix levels outer to their declarations or to be redefined at levels inner to their use.

The concept of attribute protection has been dealt with. The two levels of protection specifications have been explained.

We have now covered all the features of the SIMULA language. Only certain system features remain.