Parametrized Classes

All Sather classes may be parametrized by one or more type parameters. Type parameters are essentially placeholders for actual types; the actual type will be known when the class is actually used. The array class, which we have already seen, is an example of a parametrized class.

As an example of a parametrized class, consider the class PAIR, which can hold two objects of arbitrary types:

	class PAIR{T1,T2} is
	   readonly attr first: T1;
	   readonly attr second: T2:
	   
	    create(a_first: T1, a_second: T2): SAME is
	      res ::= new;	
	      res.first := a_first;
	      res.second := a_second;	
	      return res;
	    end;
         end;
We can use this class to hold a pair of integers or a pair of an integer and a real etc.
	c ::= #PAIR{INT,INT}(5,5);     -- Holds a  pair of integers
	d ::= #PAIR{INT,FLT}(5,5.0);   -- Holds an integer and a FLT
	e ::= #PAIR{STR,INT}("this",5);-- A string and an integer 
	f: INT := e.second;
	g: FLT := d.second;
Thus, instead of defining a new class for each different type of pair, we can just parametrize the PAIR class with different parameters. Note that parametrization plays an extremely important role in a language with strong typing like Sather. We could imagine defining a pair of $OBs
	class OB_PAIR is
	   readonly attr first: $OB;
	   readonly attr second: $OB;
	   
	    create(a_first, a_second: $OB): SAME is
	      res ::= new;	
	      res.first := a_first;
	      res.second := a_second;	
	      return res;
	    end;
	end;
There is no problem with defining OB_PAIR objects; in fact, it looks a little simpler.
	c ::= #OB_PAIR(5,5);     -- Holds a  pair of integers
	d ::= #OB_PAIR(5,5.0);   -- Holds an integer and a FLT
	e ::= #OB_PAIR("this",5);-- A string and an integer
However, when the time comes to extract the components of the pair, we are in trouble:
	-- f: INT := e.second; ILLEGAL! second is declared to be a $OB
	-- s: STR := e.first; ILLEGAL! second is declared to be a $OB
We can typecase on the return value:  
	f_ob: $OB := e.second; 
	f: INT;
	typecase f_ob when INT then f := f_ob end;
	s_ob: $OB := e.first;
	s: STR;
	typecase s_ob when STR then s := s_ob end;
The above code has the desired effect, but is extremely cumbersome. Imagine if you had to do this every time you removed an INT from an ARRAY! Of course we could envisage some more compact syntax for the above operation, but it has been left cumbersome for a different reason - it shows clearly a spot in the code where a run-time type exception may occur. In fact, a typecase is the only place where a run-time type error may occur in Sather! The error would occur if, inf fact, "f_ob" is not really an INT, but is actually some other type. Then no branch of the typecase would be satisfied, and an exception would be raised. We can take care of the exception by using an else clause  
	f_ob: $OB := e.second; 
	f: INT;
	typecase f_ob when INT then f := f_ob 
	else #ERR+"f_ob is not of the right type!\n" end;
The parametrized version of the pair container gets around all these problems by essentially annotating the type of the container with the types of the objects it contains; the types of the contained objects are the type parameter.

Type Bounds

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.

For our example, we will return to employees and manageers. Recall that the employee abstraction was defined as:

type $EMPLOYEE is
    name: STR;
    id: INT;
end;
We can now build a container class that holds employees. The container class makes uses 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; -- NOTE: Uses type-bound
		    if name.size > cur_longest then
			cur_longest := name.size;
		    end;
		end;
		return cur_longest;
	    end;
	end;
The main 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.

   

Parametrized Abstract Classes

Sather abstract classes may be similarly parametrized by any number of type parameters. Each type parameter may have an optional type bound; this forces any actual parameter to be a subtype of the corresponding type bound. Given the following definitions,

abstract class $A{T < $BAR} is
  foo(b:T): T;
end;
type $BAR is end;
class BAR < $BAR is end;
we may then instantiate an abstract variable a:$A{BAR}. BAR instantiates the parameter T and hence must be under the type bound for T, namely $BAR. If a type-bound is not specified then a type bound of $OB is assumed.

   

Why have typebounds?

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.

 

How are different parametrizations related?

It is sometimes natural to want a $LIST{MY_FOO} < $LIST{$MY_FOO}. Sather, however, specifies no subtyping relationship between various parametrizations. The reason is contravariance. Consider the case where the parameter type is used to specify an argument type in a routine

abstract $LIST{T} is
   append(element: T);
end;
abstract class $POLYNOMIAL is 
end;
abstract class $SQUARE < $POLYNOMIAL is
end;
a: $LIST{$POLYNOMIAL};
b: $LIST{$SQUARE};
We may wish to have a < b, so that we can pass a list of squares to any routine that can deal with a list of polynomials. However, contravariance would not permit $LIST{$SQUARE} < $LIST{$POLYNOMIAL}, since append(element: $SQUARE) cannot be under append(element: $POLYNOMIAL) due to contravariance.

 


Benedict A. Gomes
Mon Apr 29 10:12:43 PDT 1996