Essentially we have seen that the dot notation form of remote accessing allows us to use the attributes of a class object from outside it. It is also used to access the attributes of text objects, which are not class objects.
The inspect statement has allowed us to assume that any identifiers used in the statement following the keyword do are possibly attributes of the class object being inspected. Thus the programs in examples 13.1 can be treated as identical. A text object may not be inspected in this way.
Example 13.1b shows the situation where an identifier inside the class is the same as an identifier in the main program. When Int is used inside the class, it refers to the declaration inside the class. When Int is used outside the class, it normally refers to the declaration in the main program block. When it is used inside an inspect statement it is always taken to refer to the declaration in the class inspected.
The rule is that the statement following the do in an inspect statement is treated as if it were inside the body of the class being inspected.
This can cause confusion when there is a name conflict, like the one in example 13.1b. It is often clearer to use dot notation in such cases. A better solution, of course, is to avoid name conflicts where possible.
Examples 13.1: Remote accessing.
a) Dot notation.
begin integer Int; class Example(Ch); character Ch; begin integer Int; text Txt; procedure Proc; OutText("Called"); end--of--Example; ref(Example) Ex1; Ex1 :- new Example('?'); OutChar(Ex1.Ch); Ex1.Proc; for Int := Ex1.Int step 2 until 6 do Ex1.Txt := "Txt" endb) inspect statement.
begin integer Int; class Example(Ch); character Ch; begin integer Int; text Txt; procedure Proc; OutText("Called"); end--of--Example; ref(Example) Ex1; Ex1 :- new Example('?'); inspect Ex1 do begin OutChar(Ch); Proc; comment Note that the first Int in the next statement now refers to the attribute of Ex1, not the declaration in the main program; for Int := Int step 2 until 6 do Txt := "Txt" end end
Class Outside contains a class declaration for Inside and other attributes. The main program contains a variable Outsider which references an object of class Outside. Using dot notation we cannot access any of the attributes of Outsider from the main program block.
Similarly, the class Outside2 is a subclass of Outside. The ref(Outside2) object, Outsider2 has attributes in addition to those of Outside, but none of these may be accessed by dot notation because the prefix Outside has a local class declaration.
N.b. the restriction refers to class declarations, not reference variable declarations.
In order to access these attributes from the main program block we must use an inspect statement as shown in example 13.2b.
Examples 13.2: Limitations of dot notation.
a) An illegal use of dot notation.
begin class Outside; begin class Inside; begin text InsideText; end..of..Inside; integer Int1; text Text1; end--of--Outside; Outside class Outside2; begin integer Int2; text Text2; end; ref(Outside) Outsider; ref(Outside2) Outsider2; Outsider :- new Outside; Outsider2 :- new Outside2; Outsider.Text1 :- Copy("Illegal"); Outsider2.Int2 := 4 end++of++programb) A legal version of the same program using inspect.
begin class Outside; begin class Inside; begin text InsideText; end..of..Inside; integer Int1; text Text1; end--of--Outside; Outside class Outside2; begin integer Int2; text Text2; end; ref(Outside) Outsider; ref(Outside2) Outsider2; Outsider :- new Outside; Outsider2 :- new Outside2; inspect Outsider do Text1 :- Copy("Legal"); inspect Outsider2 do Int2 := 4 end++of++program
We saw in chapter 11 that it is possible to reference all objects which are subclasses of one parent class using a variable declared as a ref to the parent. This enabled us to form a linked list of objects of different subclasses, for instance. At the same time we saw the limitation of this, in that the subclass attributes could not be accessed through these variables, only the attributes of the parents. Neither dot notation nor simple inspect statements can get around this problem.
Another problem with remote accessing is that it is clearly nonsense to use a reference variable before it has been pointed at an object (assigned a reference). Such an attempt is in fact illegal and will cause a runtime error to be signalled.
To get around these problems we can use extensions of the inspect statement.
Consider examples 13.3. The first program would simply ignore some elements of the array References, since not all the reference variables in this array have been pointed at objects of class Example. The second program, on the other hand, will detect such variables and report which they are. It does this by adding an otherwise clause to its inspect statement.
The syntax of our extended inspect statement is now the keyword inspect, followed by a reference to a class object, followed by the keyword do, followed by a statement, optionally followed by an otherwise clause.
The syntax of an otherwise clause is the keyword otherwise followed by a statement.
The reason for choosing inspect as the keyword for this type of statement is perhaps a little clearer now. The meaning of an inspect statement makes it a suitable choice. The class object is first inspected to see if it is None. If not, it is used to provide the class which is assumed to be remotely accessed in the statement following the keyword do, which is then executed. If it is None, the statement following the keyword otherwise is executed, where present. If there is no otherwise clause, the use of None will cause the statement following do to be skipped.
Notice that we have used the phrase "reference to a class object", for what comes between inspect and do. This is usually an indentifier of ref to class type, possibly subscripted, but could be any expression yielding a ref to class result. An example of an alternative could be a type procedure, whose type is ref to a class and which returns a reference to a class object, like SysIn and SysOut. Chapter 17 will make a lot of use of such type procedures.
The statement following do acts as if it were inside the class object being inspected. All identifiers are first matched against declarations within the class and its prefixing classes. In technical terms, the qualification of this statement is the same as the class inspected.
This extension to the inspect statement is quite useful, but it does not allow us to do anything that was not already possible. Example 13.3c shows the same program written without the otherwise, but still checking for None. To see the real power of the inspect concept, we must extend the definition still further.
Examples 13.3: Detecting None with otherwise.
a) A program which fails to check for None.
begin class Example; begin text Message; Message :- Copy("Legal") end..of..Example; ref(Example) array References(0:10); integer Counter; comment Only every other element assigned to; for Counter := 0 step 2 until 10 do References(Counter) :- new Example; comment Every element inspected; for Counter := 0 step 1 until 10 do inspect References(Counter) do begin OutText(Message); OutImage end--of--inspecting--element end++of++programb) The same program using otherwise to check for None.
begin class Example; begin text Message; Message :- Copy("Legal") end..of..Example; ref(Example) array References(0:10); integer Counter; for Counter := 0 step 2 until 10 do References(Counter) :- new Example; comment Still inspect every element, but use otherwise to report nones; for Counter := 0 step 1 until 10 do inspect References(Counter) do begin OutText(Message); OutImage end--of--the--connection--block otherwise begin OutText("Illegal"); OutImage end--of--the--othrwise--clause end++of++programc) The same program without inspect.
begin class Example; begin text Message; Message :- Copy("Legal") end..of..Example; ref(Example) array References(0:10); integer Counter; for Counter := 0 step 2 until 10 do References(Counter) :- new Example; comment Use direct checking and dot notation; for Counter := 0 step 1 until 10 do begin if References(Counter)=/=None then begin OutText(References(Counter).Message); OutImage end--of--legal--reference--case else begin OutText("Illegal"); OutImage end--of--the--None--case end**of**for**loop end++of++program
Example 13.4 shows the use of an inspect statement with when clauses to process a linked list. All the objects on the list belong to classes sharing a common prefix. They are linked by variables declared as ref to this common prefix.
When we come to process each object, we wish to treat it differently according to its innermost type. In fact we need not select the innermost, but we are restricted to the level of qualification that we choose, so that attributes at inner levels would be invisible within the when clause.
This extension's syntax replaces the single keyword do with one or more when clauses. The otherwise clause remains optional, but its meaning changes slightly.
Each when clause consists of the keyword when, followed by the name of a class, followed by the keyword do, followed by a statement. Each occurrence of the keyword when marks the beginning of a new when clause.
The when clause contains an identifier matching a class declared in the program and visible to the inspect statement. The execution of an extended inspect statement tries to match the qualification of the inspected object or one of its prefixing classes to the class identifiers in each when clause in turn. Once a match is found, the statement following the do in that when clause is executed.
The statement following the do in a when clause is treated as if it were declared inside the class whose identifier is used in that clause. Thus, in example 13.4, when the objects on the list are inspected this determines which prefix level the declaration of procedure Print is taken from. Although the objects from the list can only be accessed as ref(A) objects, within each when clause the attributes of the appropriate sub-classes can be used.
Note the order of the when clauses. Inner classes must be tested for before outer, if we wish them to be distinguished. This is an important consequence of the rules given above.
If no match is found, the statement is skipped unless it contains an otherwise clause, in which case that is executed.
Within the extended inspect statement the otherwise clause, where present, is executed if the referenced object is None, as before, and also if its qualification fails to match any of the when clauses. In programs which are still being developed this can be used to handle currently missing cases, by printing out a suitable warning. It can also catch those cases which you have forgotten to deal with!
Example 13.4: Mixed list processing using when.
begin class A; begin ref(A) Link; procedure Print; OutText("Class A"); end..of..A; A class B; begin procedure Print; OutText("Class B"); end..of..B; A class C; begin procedure Print; OutText("Class C"); end..of..C; B class D; begin procedure Print; OutText("Class D"); end; C class E; begin procedure Print; OutText("Class E"); end..of..E; ref(A) Head, NextA; for NextA :- new B, new C, new D, new E do begin NextA.Link :- Head; Head :- NextA end--of--building--mixed--list; while NextA=/=None do begin NextA.Print; ! Always prints "Class A"; inspect NextA when E do Print when D do Print when C do Print when B do Print when A do Print otherwise OutText("Not a predicted subclass of A"); OutImage; NextA :- NextA.Link end--of--while--loop end++of++program
Input is terminated by a line containing ".end" as its start.
The program first reads each record into an object of one of four classes designed to hold their particular data. All four of these classes are prefixed by class Linker, which contains pointers to allow linked lists to be built up. At first a single list of all the objects is constructed using the first pointer variable in Linker.
This list is then processed, with the aid of a when clause, and the objects placed on one from each of two pairs of lists, using the other two pointers in Linker.
Finally these lists are written out separately.
The two complementary actions of sorting and merging lists are fundamental to many uses of computers. The addition of facilities to insert new items in a list and to modify existing items gives a powerful basic set of tools for manipulating data. We are now in a position to build such tools and extend them as the need arises.
Example 13.5: Merging and sorting using inspect.
begin class Linker; begin ref(Linker) Next, Sex, Employment; text ID; end--of--Linker; Linker class Male_Dancer; begin ! Should contain full details; end--of--Male--Dancer; Linker class Female_Dancer; begin ! Should contain full details; end--of--Female--Dancer; Linker class Male_Accountant; begin ! Should contain full details; end--of--Male--Accountant; Linker class Female_Accountant; begin ! Should contain full details; end--of--Female--Accountant; procedure Onto_List(Entry,Gender,Occupation); name Gender,Occupation; ref(Linker) Entry,Gender,Occupation; begin Entry.Sex :- Gender; Gender :- Entry; Entry.Employment :- Occupation; Occupation :- Entry end--of--Onto--List; procedure Write_List(Heading, ListHead); text Heading; ref(Linker) ListHead; begin Boolean SexList; SexList := (ListHead==Males or ListHead==Females); OutImage; OutText(Heading); OutImage; OutImage; while ListHead=/=None do begin OutText(ListHead.ID); OutImage; if SexList then ListHead :- ListHead.Sex else ListHead :- ListHead.Employment end end--of--Write--List; text Line; ref(Linker) NextEntry,List,Males,Females,Dancers,Accountants; comment First read the input onto a single list; InImage; Line :- Blanks(80); while SysIn.Image.Strip ne ".end" do begin Line.SetPos(1); Line := SysIn.Image; if Line.GetChar='F' then begin if Line.GetChar='D' then NextEntry :- new Female_Dancer else NextEntry :- new Female_Accountant end else begin if Line.GetChar='A' then NextEntry :- new Male_Accountant else NextEntry :- new Male_Dancer end; InImage; NextEntry.ID :- Copy(SysIn.Image); InImage; NextEntry.Next :- List; List :- NextEntry end; comment Now process the main list, forming threaded lists; NextEntry :- List; while NextEntry=/=None do begin inspect NextEntry when Male_Dancer do Onto_List(NextEntry,Males,Dancers) when Female_Dancer do Onto_List(NextEntry,Females,Dancers) when Male_Accountant do Onto_List(NextEntry,Males,Accountants) when Female_Accountant do Onto_List(NextEntry,Females,Accountants); NextEntry :- NextEntry.Next end; comment Now write out by lists; if Females=/=None then Write_List("Females",Females); if Males=/=None then Write_List("Males",Males); if Dancers=/=None then Write_List("Dancers",Dancers); if Accountants=/=None then Write_List("Accountants",Accountants) end++of++program
The restriction means that in a program containing declarations of class A, A class B and B class C, a variable declared as ref(B) can normally only be used as follows:
An alternative to this is known as "instantaneous" qualification. It does not allow a series of choices, like the full inspect statement with when clauses. Nor does it allow us to avoid references pointing to None. In these ways it is less powerful than inspection. On the other hand, it can be used to restrict as well as extend the levels of attributes which may be used.
The feature used is based on the keyword qua and example 13.6 show two ways of using it.
13.6a shows it used rather like a when clause. It allows legal access to a class object at a level inner to that at which it is being referenced.
13.6b shows its power in the reverse direction. It is used to access the procedure POuter declared in class A, through a reference to A class B, even though POuter is redeclared as an integer inside B.
13.6c shows an illegal use of qua. An attempt is made to access an inner level when the object referenced is not of the inner type. This would produce a runtime error.
Example 13.6: Uses of qua.
begin class A; begin integer I,J; end--of--A; A class B; begin text I,J; end--of--B; ref(A) ARef1, ARef2; ref(B) BRef; ARef1 :- new B; BRef :- new B; ARef2 :- new A; ! a); ARef1 qua B.I :- Copy("Getting inside"); ! b); BRef qua A.I := 4; ! c); ARef2 qua B.I :- Copy("No inside to get to") end++of++program
This group is still a reference to the class object, but it is accessed at the prefix level defined by the class specified. Only the attributes of this class and any prefixes to it may be accessed.
One very useful but much simpler feature in SIMULA is the ability of an object to reference itself. It is achieved by using the keyword this. Example 13.7 shows the use of this.
Formally the keyword this, followed by a name of a class is a reference to the class object containing it. The class name must be the one qualifying that object or on its prefix chain.
To make this clearer, consider example 13.7. Here the generation of a new object of class Linker, or prefixed by it, automatically inserts that object in the list whose head is passed as a parameter to it. Without the use of this, it would be impossible to perform such an action.
Using this many more actions can be included as local procedure attributes or local statements in the definition of a class. This is very important to the idea of object oriented programming.
Example 13.7: Using this in 13.5.
begin class Linker; begin ref(Linker) Next, Sex, Employment; text ID; procedure Add_to_List(LHead); name LHead; ref(Linker) LHead; begin Next :- LHead; LHead :- this Linker end..of..Add..to..List; procedure Onto_Lists(Gender,Occupation); name Gender,Occupation; ref(Linker) Gender,Occupation; begin Sex :- Gender; Employment :- Occupation; Gender :- Occupation :- this Linker end..of..Onto..Lists; InImage; ID :- Copy(SysIn.Image); InImage; end--of--Linker; Linker class Male_Dancer; begin ! Should contain full details; end--of--Male--Dancer; Linker class Female_Dancer; begin ! Should contain full details; end--of--Female--Dancer; Linker class Male_Accountant; begin ! Should contain full details; end--of--Male--Accountant; Linker class Female_Accountant; begin ! Should contain full details; end--of--Female--Accountant; procedure Write_List(Heading, ListHead); text Heading; ref(Linker) ListHead; begin Boolean SexList; SexList := (ListHead==Males or ListHead==Females); OutImage; OutText(Heading); OutImage; OutImage; while ListHead=/=None do begin OutText(ListHead.ID); OutImage; if SexList then ListHead :- ListHead.Sex else ListHead :- ListHead.Employment end end--of--Write--List; ref(Linker) NextEntry,List,Males,Females,Dancers,Accountants; text Line; comment First read the input onto a single list; InImage; Line :- Blanks(80); while SysIn.Image.Strip ne ".end" do begin Line.SetPos(1); Line := SysIn.Image; if Line.GetChar='F' then begin if Line.GetChar='D' then NextEntry :- new Female_Dancer else NextEntry :- new Female_Accountant end else begin if Line.GetChar='A' then NextEntry :- new Male_Accountant else NextEntry :- new Male_Dancer; end; NextEntry.Add_to_List(List) end; comment Now process the main list, forming threaded lists; NextEntry :- List; while NextEntry=/=None do begin inspect NextEntry when Male_Dancer do Onto_Lists(Males,Dancers) when Female_Dancer do Onto_Lists(Females,Dancers) when Male_Accountant do Onto_Lists(Males,Accountants) when Female_Accountant do Onto_Lists(Females,Accountants); NextEntry :- NextEntry.Next end; comment Now write out by lists; if Females=/=None then Write_List("Females",Females); if Males=/=None then Write_List("Males",Males); if Dancers=/=None then Write_List("Dancers",Dancers); if Accountants=/=None then Write_List("Accountants",Accountants) end++of++program
References to class objects may be declared and passed as parameters to both procedures and classes. They are passed to both by reference as a default. They may be passed by name to procedures but not to classes. They may never be passed by value. (See tables 6.1 and 9.1.)
The actual reference passed when the procedure is called or the class object is generated, must be compatible with the class specified in the declaration. This means that its qualification must be equal or inner to the declared class, just as in a reference assignment.
13.3 Extend Into in 13.2, giving it a second parameter which is a ref to Linker. When called it should now insert the object through which it is accessed into the list after the object referenced by its second parameter. Beware of None. What modes should be used here?
We have seen the full use of the inspect statement in both its simple and extended forms.
We have used the otherwise clause to cope with cases of references to None.
We have used the when clause to differentiate subclasses referenced through a common prefix. This has allowed us to access attributes which were not available through this prefixing class. Otherwise allowed us to cater for unwanted subclasses along with None.
The use of qua has been shown to allow us both to extend to inner prefix levels and restrict to outer prefix levels the qualification of class object references.
We have examined ways in which this can be exploited, allowing objects to reference themselves and statements inspecting objects to reference those objects.
A summary of the use of references to class objects as parameters has been presented.