Classes may be generic. Genericity (often called parametric polymorphism) allows classes to include unspecified types in their class description, which are instantiated when an object of this class is created. This allows a more general form of code reuse. A list, for instance, can be defined to hold elements of some type, where the type is not specified at the time the class is implemented. The actual type of the list elements is then specified when an object of class list is created. Several instances of the list can exist, one holding elements of type Integer, another one elements of type String, and so on.
Most of the BNF rules involved in genericity have been mentioned above. Here we repeat the relevant parts:
BNF:
class-decl ::= class identifier [ "<" generics_list ">"] is
class-definition
generics-list ::= generics-decl { "," generics-decl }
generics-decl ::= formal-generic-param [ is class-type ]
formal-generic-param ::= identifier
var-decl ::= ident-list ":" type [ initilisation ]
type ::= class-type
| formal-generic-param
| ...
class-type ::= identifier [ "<" actual-generics-list ">" ]
actual-generics-list ::= type { "," type }
The following example shows part of a class definition for a generic stack and code fragments declaring and using the stack class, instantiating it once as an Integer Stack and once as a Stack of Figures.
class Stack <ELEM_TYPE> is internal var
st: Array <ELEM_TYPE>
interface
... routines push (elem: ELEM_TYPE) is
== push elem on stack pop -> (elem: ELEM_TYPE) is
== pop top of stack and return it in elem ... end class
class client uses Stack, Figure internal
var
s1: Stack <Integer>
s2: Stack <Figure>
f: Figure ... s1.push (42)
s2.push (f) end class
Within the generic class (here: Stack) the formal generic parameter (ELEM_TYPE) is a valid type. It can be used for the declaration of variables, parameters and return values.
Classes may have more than one generic parameter. The list of types in the instantiation (in the variable declaration), the actual generic parameters, must have the same number of elements as the list of identifiers in the class header (the formal generic parameters).
Example:
class List <T1, T2> is
...
end class
class Client
uses List
internal
var l: List <String, Integer>
...
It is time to revisit the relationship between classes and types. Up to now, we did not distinguish very carefully between classes and types. This was no problem because, as long as no generic classes are involved, each class corresponds to exactly one type, and the type is referred to by using the class's name. Consider
var
p : Person
We sometimes said "p is of class Person" or "p is of type Person". A more accurate (but rather clumsy) way to describe this situation is "p is declared of the type that is defined by class Person".
This now changes with generic classes.
Consider the class Stack shown above. Stack is a class, but because it is generic, it does not correspond to one type, but potentially to many types. Since variable declarations need types (not classes), the following declaration is wrong:
var
s : Stack - - ERROR ERROR ERROR !
Instantiating the generic parameters of a generic class generates a type. In other words: Stack is not a type, but Stack<Integer> is. The following declaration is legal:
var
s : Stack <Integer>
Almost everywhere in Blue code where we have seen class names so far, it is actually a type that is expected. (The BNF has always shown this correctly.) For example, the actual generic parameters themselves are types, not classes. Thus the following declaration is an error:
var
n: Array <Stack> - - ERROR!
The declaration should read
var
n: Array <Stack <Integer>>
The same is true for a supertype of a class, e.g.
class PersonList is List <Person> ...
and for type specifications in the is expression (see section 10.3).
The only place where class names, rather than a type, are specified is in the uses clause of a class.
Since the dynamic type (the real type at runtime) of an object held in a variable declared of a formal generic type is not known, no special operation can be allowed on that object.
Consider:
class List <ELEM_TYPE> is
...
var
head : ELEM_TYPE
... end class
At compile time of class List the dynamic type of head cannot be known. Only operations that are allowed on all objects of any class are allowed on variables of this type. These operations are listed in section 3.2. They are assignment and equality check (:=, =, <>).
The generic type in generic classes can be constrained.
Example:
class List <T is Comparable> is ...
end class
In this example, all actual generic parameters for List in instantiations of this class have to be of a subclass of Comparable (or of class Comparable itself).
As a result of this, operations defined for Comparable can be used within the class List on variables declared of type T.