Previous | Table of Contents | Next |
The phrase (Kind: InstrumentKinds) defines a special record component called the discriminant; in other languages, such a field is called a tag. The discriminant serves to parameterize the type; a typical variable declaration might be
S: Instrument(Kind => Speedometer);
which constrains S to that variant. If I wanted to allow an instrument variable to contain different instruments at different timesto be unconstrained or mutableI could provide a default discriminant value in the type declaration, say,
type Instrument (Kind: InstrumentKinds := Gauge) is record...
in which case I could declare
I: Instrument;
whose value is initially a gauge but could change over time.
In Ada, a variant type declaration must include a discriminant component; free union types without discriminants such as those in C or Pascal do not exist. Further, Ada ruleswhose details I do not belabor hereensure that in a mutable variant object, the value of the discriminant is always consistent with the actual values in the variant part.
The variant parts are specified with a case construct, and operations on variant objects typically use case statements to manipulate the variant parts. Variant records represent yet another kind of static polymorphism in that mutable objects can effectively change their type over time and operations can be written that select among the various type possibilities.
The inconvenience in using a variant record type is that if, in the future of the program, a new variant must be added, it must be added to the original type declaration. Worse, each operation that manipulates objects of the type must be explicitly modified with a new case choice to account for the new variant. In many applications, this additional maintenance is quite acceptable; further, the static solution is appealing to developers of real-time systems in which predictability of execution time and space overhead is of paramount concern.
For full OOP, programmers are interested precisely in being able to extend a variant type ad infinitum without imposing any changes to existing code. In other words, we are interested in inheritance and dynamic polymorphism. In Ada 95, this is provided by a syntactically simple extension of the type system.
10.4.3.2. Type Extension: Ada 95 Tagged Types
Support in Ada 95 for full inheritance and polymorphism is done using type extension, building on the existing type, type derivation, and package capabilities of Ada 83. I introduce this in the context of the automobile instrument cluster mentioned in the last section. By the end of this discussion, I display a dashboard such as
Speed : 45 Miles per Hour Fuel : 60 % Water : <****************....> Oil : <******..............> Time : 12:15:00 Chronometer : <<79976>>
using the following type hierarchy:
Recall that in the variant-record solution, adding a new variant required adding a new case choice to every operation on objects of the variant type. Using type extension, I can add new variants without touching existing code. HB.Instruments is the interface of a package exporting a root type Instrument.
1 package HB.Instruments is 2 3 type Instrument is abstract tagged record 4 Name: String(1 .. 14):= (others => ); 5 end record; 6 7 procedure Set_Name(I: in out Instrument; S: String); 8 procedure Display_Value(I: Instrument); 9 10 end HB.Instruments;
The reserved word tagged is the key to type extension. Adding it to a record type declaration causes the declaration to function as the root of a type hierarchy. I can derive new types from it, as in the earlier Credit_Card example, but now I can also add components to the derived type.
Primitive operations of the parent typeessentially those procedures and functions declared in the same package interface, just after the parent typeare inherited by the child type but can be overridden by similar operations with the same name but with formal parameters of the parent type replaced by those of the child type. In this example, Set_Name and Display_Value are primitive operations of the type Instrument.
The reserved word abstract mentioned in the type declaration indicates that this type serves simply as a root for derivations; that is, I cannot declare any objects of the type. For an abstract type, it is possible to declare abstract operations; an abstract operation has no function or procedure body and is used simply to serve as a root operation for inheritance. An ordinary primitive operation can be overridden; an abstract primitive operation must be overridden.
Now the child package HB.Instruments.Basic provides a set of three instrument kinds, all derived from Instrument.
1 package HB.Instruments.Basic is 2 3 subtype Speeds is Integer range 0 .. 85; -- mph 4 5 type Speedometer is new Instrument with record 6 Value: Speeds; 7 end record; 8 9 procedure Set_Value(S: in out Speedometer; V: Speeds); 10 procedure Display_Value(S: Speedometer); 11 12 subtype Percent is Integer range 0 .. 100; 13 14 type Gauge is new Instrument with record 15 Value: Percent; 16 end record; 17 18 procedure Display_Value(G: Gauge); 19 20 type Graphic_Gauge is new Gauge with record 21 Size : Integer:= 20; 22 Fill : Character:= *; 23 Empty: Character:= .; 24 end record; 25 26 procedure Display_Value(G: Graphic_Gauge); 27 28 end HB.Instruments.Basic;
Lines 5-7 declare a new type Speedometer, derived from Instrument. Each Speedometer object has the name component of Instrument, but also an additional component Value, specific to the new type. Speedometer is not abstract; I can declare objects of this type, which of course was just the intent.
Previous | Table of Contents | Next |