4.3 Matching Against Type Patterns
Usually, the distinction between the dynamic and the static type is unimportant. For example, with the simple min
method defined above, the caller will know that the type of the result is a subtype of the least-upper-bound of the dynamic types of the two arguments. Given the static knowledge that both arguments are of some dynamic type that is a subtype of a particular static type, the caller can infer the static knowledge that the result is some subtype of that static type. Static type information already implies only that the dynamic type of some expression is some subtype of the static type, so calculating static approximations to implicitly-bound type variables is what the type checker has been doing all along.
In two circumstances, however, the distinction between instantiating a type parameter with a dynamic type versus a static type is important. If a implicitly-bound type parameter is used as a normal type for another declaration, i.e., as an upper bound type, then legal actual parameters must be known to be equal to or subtypes of the implicitly bound type variable. For example, if min
were rewritten as follows:
method
min(x1:'T <= comparable, x2:T):T {
if (x1 < x2, { x1 }, { x2 })}
the second argument would be required to be a subtype of the dynamic type of the first argument. This requirement could be quite difficult to guarantee statically and is probably not what the programmer meant. Type parameters are usually used directly as type declarations when they are bound to the instantiating parameter of a parameterized type, as in the following method:
Here,method
store(a:array['T], index:int, value:T):void { -- storevalue as the indexth element of the array a
}
T
will be bound to the type of the elements of the array, specified when the array was created, and usually the value
argument will be known to be a subtype of that type at the call site, perhaps because it had just been extracted from an array of similar type.
The distinction between dynamic types and static types for instantiation also appears when instantiating a parameterized object. For example, one way to write the new_array
method might be the following:
Given an initial value of dynamic typemethod
new_array(size:int, initial_value:'T):array[T] {concrete object
isa
array[T] { size := size, initial_value := initial_value } }
T
, an array is returned with the type T
as the instantiating value. Because of the fetch and store operations defined on arrays, this array will only be able to contain elements that are subtypes of the dynamic type of its initial value. Usually, this would be too restrictive. To correct this problem, the real method to create a new array is explicitly parameterized with the desired type of the elements:
Instantiations of parameterized objects record their instantiating types as part of their dynamic run-time state. The instantiating types are used to determine the subtyping relation of the object and when matching the parameterized object's type against a type pattern of the form typemethod
new_array[T](size:int, initial_value:T):array[T] {concrete object
isa
array[T] { size := size, initial_value := initial_value } }
[...,'Ti
<=
typei,...]
.
Generated with Harlequin WebMaker