Parametric Polymorphism and Templates


Parametric polymorphism (or polymorphism for short) offers a flexible and effective approach to supporting code reuse. For instance, given a pair (v1, v2) where v1 is a a boolean and v2 a character, the function swap_bool_char defined below returns a pair (v2, v1):

fun swap_bool_char (xy: @(bool, char)): @(char, bool) = (xy.1, xy.0)
Now suppose that a pair of integers need to be swapped, and this results in the implementation of the following function swap_int_int:
fun swap_int_int (xy: @(int, int)): @(int, int) = (xy.1, xy.0)
The code duplication between swap_bool_char and swap_int_int is obvious, and it can be easily avoided by implementing a function template as follows:
fun{a,b:t@ype} swap (xy: @(a, b)): @(b, a) = (xy.1, xy.0)
Now the functions swap_bool_char and swap_int_int can simply be replaced with swap<bool,char> and swap<int,int>, respectively. The function template swap cannot be compiled into executable binary code directly as the sizes of type variables a and b are unknown: The special sort t@ype is for classifying types whose sizes are unspecified. If swap<T1,T2> is used for some types T1 and T2 of known sizes, then an instantiation of swap is created where type variables a and b are replaced with T1 and T2, respectively, and then compiled into executable binary code. For those who know the feature of templates in C++, this should sound rather familiar. In contrast to swap, swap_type_type is defined below as a polymorphic function (rather than a function template):
fun swap_type_type {a,b:type} (xy: @(a, b)): @(b, a) = (xy.1, xy.0)
This function can be compiled into executable binary code as the sizes of type variables a and b are known: The special sort type is for classifying types whose sizes equal exactly one word, that is, the size of a pointer. For example, the size of a string is one word, and the size of any declared datatype is also one word. Given strings s1 and s2, an application of swap_type_type to @(s1, s2) can be written as follows:
swap_type_type {string,string} @(s1, s2)
where the expression {string,string} is often referred to as a static argument. As in this case, most static arguments do not have to be provided explicitly since they can be automatically inferred. However, such static arguments, if provided, can often enhance the quality and precision of the error messages reported in case of typechecking failure. This is a topic to be explored elsewhere in great depth.

Template Declaration and Implementation

Often, the interface for a template may need to be declared alone. For instance, the interface for the above swap function template can be declared as follows:
extern fun{a,b:t@ype} swap (xy: @(a, b)): @(b, a)
Just like a declared function interface, a declared template interface can be implemented. For instance, the following code implements the interface declared for the swap function template:
implement{a,b} swap (xy) = (xy.1, xy.0)
This form of template implementation is often referred to as generic template implementation in contrast to specialized template implementation presented as follows. It is also allowed to implement specialized templates in ATS. For instance, the following code implements the above swap function template that is specialized with the type variables a and b being set to int and int, respectively:
implement swap<int,int> (xy) = let
  val x = xy.0 and y = xy.1; val s = x + y in (s - x, s - y)
end // end of [swap<int,int>]

The code used for illustration is available here.