When writing more complex parametrized classes, it is frequently useful to be able to perform operations on variables which are of the type of the parameter. For instance, in writing a sorting algorithm for arrays, you might want to make use of the "less than" operator on the array elements.If a parameter declaration is followed by a type constraint clause ('<' followed by a type specifier), then the parameter can only be replaced by subtypes of the constraining type. If a type constraint is not explicitly specified, then '< $OB' is taken as the constraint. A type constraint specifier may not refer to SAME'. The body of a parameterized class must be type-correct when the parameters are replaced by any subtype of their constraining types this allows type-safe independent compilation.
For our example, we will return to employees and managers. Recall that the employee abstraction was defined as:
abstract class $EMPLOYEE is name:STR; id:INT; end; |
We can now build a container class that holds employees. The container class makes use of a standard library class, a LIST, which is also parametrized over the types of things being held.
class EMPLOYEE_REGISTER{ETP < $EMPLOYEE} is private attr emps:LIST{ETP}; create:SAME is res ::= new; res.emps := #; return res; end; add_employee(e:ETP) is emps.append(e); end; n_employees:INT is return emps.size; end; longest_name:INT is -- Return the length of the longest employee name i:INT := 0; cur_longest:INT := 0; loop until!(i=n_employees); employee:ETP := emps[i]; name:STR := employee.name; -- The type-bound has the ".name" routine if name.size > cur_longest then cur_longest := name.size; end; end; return cur_longest; end; end; |
The routine of interest is "longest_name". The use of this routine is not important, but we can imagine that such a routine might be useful in formatting some printout of employee data. In this routine we go through all employees in the list, and for each employee we look at the "name". With the typebound on ETP, we know that ETP must be a subtype of $EMPLOYEE. Hence, it must have a routine "name" which returns a STR.
If we did not have the typebound (there is an implicit typebound of $OB), we could not do anything with the resulting "employee"; all we could assume is that it was a $OB, which is not very useful.
The purpose of the type bound is to permit type checking of a parametrized class over all possible instantiations. Note that the current compiler does not do this, thus permitting some possibly illegal code to go unchecked until an instantiation is attempted.
The need for supertyping clauses arises from our definitition of type-bounds in parametrized types. The parameters can only be instantiated by subtypes of their type bounds.
You may, however, wish to create a parametrized type which is instantiated with classes from an existing library which are not under the typebound you require. For instance, suppose you want to create a class PRINTABLE_ SET, whose parameters must support both hash and the standard string printing routine str. The library contains the following abstract classes.
abstract class $HASH < $IS_EQ is hash:INT; end; abstract class $STR is str:STR; end; |
However, our PRINTABLE_SET{T} must take all kinds of objects that support both $HASH and $STR, such as integers, floating point numbers etc. How do we support this, without modifying the distributed library?
abstract class $HASH_AND_STR > INT, FLT, STR is hash:INT; str:STR; end; class PRINTABLE_SET{T < $HASH_AND_STR} is -- Set whose elements can be printed str:STR is res:STR := ""; loop res := res + ",".separate!(elt!.str); end; return res; end; end; |
The PRINTABLE_SET class can now be instantiated using integers, floating point numbers and strings. Thus, supertyping provides a way of creating supertypes without modifying the original classes (which is not possible if the original types are in a different library).
Note that this is only useful if the original classes cannot be modified. In general, it is usually far simpler and easier to understand if standard subtyping is used.
A more complicated example arises if we want to create a sorted set, whose elements must be hashable and comparable. From the library we have.
abstract class $HASH < $IS_EQ is hash:INT; end; abstract class $IS_LT{T} < $IS_EQ is -- comparable values is_lt(elt:T):BOOL; end; |
However, our SORTABLE_SET{T} must only take objects that support both $HASH and $IS_LT{T}
abstract class $ORDERED_HASH{T} < $HASH, $IS_LT{T} is end; class ORDERED_SET{T < $ORDERED_HASH{T}} is -- Set whose elements can be sorted sort is -- ... uses the < routine on elements which are of type T end; |
The above definition works in a straightforward way for user classes. For instance, a POINT class as defined below, can be used in a ORDERED_SET{POINT}
class POINT < $ORDERED_HASH{POINT} is ... -- define hash:INT and is_lt(POINT):BOOL |
But how can you create an ordered set of integers, for instance? The solution is somewhat laborious. You have to create dummy classes that specify the subtyping link for each different parametrization of $ORDERED_HASH
abstract class $DUMMY_INT > INT < $ORDERED_HASH{INT} is end; abstract class $DUMMY_STR > STR < $ORDERED_HASH{STR} is end; abstract class $DUMMY_FLT > FLT < $ORDERED_HASH{FLT} is end; |
Note that the above classes are only needed because we are not directly modifying INT and FLT to subtype from $ORDRED_HASH{T}. In the following diagram , recall that since there is no relationship between different class parametrizations, it is necessary to think of them as separate types.