Template-Based Support for Late-Binding

When asked about the meaning of object-oriented programming (OOP), Alan Kay once said that OOP to him meant only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

In ATS, function templates can provide a highly flexible approach to supporting late-binding (of function calls). Let us first take a look at a simple example to see why late-binding can be so desirable. The following code declares a datatype intfloat such that each value of this declared type represents either an integer or a floating point number (of double precision):

// datatype intfloat = INT of int | FLOAT of double //

In order to print values of the type intfloat, we can implement print_intfloat as follows:

// fun print_intfloat (x: intfloat): void = ( case+ x of | INT(int) => print_int(int) | FLOAT(float) => print_double(float) ) //

where print_int and print_double are monomorphic functions for printing an integer and a floating point number (of double precision), respectively. There are certainly many different ways to print integers and floating point numbers, but print_intfloat only uses a particular one for integers (via print_int) and a particular one for floating point numbers (via print_double). One possibility of avoiding this form of extreme inflexibility is to define a higher-order function fprint_intfloat as follows:

// fun fprint_intfloat ( x: intfloat , print_int: int -> void , print_double: double -> void ) : void = ( case+ x of | INT(int) => print_int(int) | FLOAT(float) => print_double(float) ) //

With fprint_intfloat, one can decide to choose implementations for print_int and print_double at a later stage. In this regard, I say that higher-order functions can support a form of late-binding. However, using higher-order functions in such a manner is not without serious problems. Basically, any function that calls print_int either directly or indirectly needs to be turned into a higher-order function, and the same applies to functions calling print_double as well. This style of programming with extensive use of higher-order functions can soon become extremely unwieldy when the number of functions grows large that need to be treated like print_int and print_double.

Instead of using higher-order functions, we can rely on template functions to support late-binding (of function calls). For example, the following code implements a template function tprint_intfloat for printing values of the type intfloat:

// extern fun{} tprint_int(int): void extern fun{} tprint_double(double): void extern fun{} tprint_intfloat(intfloat): void // (* ****** ****** *) // implement tprint_int<> (x) = print_int(x) implement tprint_double<> (x) = print_double(x) // (* ****** ****** *) // implement tprint_intfloat<> (x) = ( case+ x of | INT(int) => tprint_int<> (int) | FLOAT(float) => tprint_double<> (float) ) //

Please note that the default implementations for tprint_int and tprint_double are based on print_int and print_double, respectively. As can be expected, the following code outputs two lines:

// val () = ( tprint_intfloat<> (INT(0)); print_newline() ) (* end of [val] *) // val () = ( tprint_intfloat<> (FLOAT(1.0)); print_newline() ) (* end of [val] *) //

where the first line consists of the string "0" and the second one the string "1.000000". The following code also outputs two lines:

local // implement tprint_int<> (x) = print! ("INT(", x, ")") implement tprint_double<> (x) = print! ("FLOAT(", x, ")") // in (* in-of-local *) // val () = ( tprint_intfloat<> (INT(0)); print_newline() ) (* end of [val] *) // val () = ( tprint_intfloat<> (FLOAT(1.0)); print_newline() ) (* end of [val] *) // end // end of [local]

where the first line consists of the string "INT(0)" and the second one the string "FLOAT(1.000000)"). In the latter case, the calls to template instances tprint_int<> and tprint_double<> are compiled according to the implementations for tprint_int and tprint_double given between the keywords local and in.

Please find on-line the file intfloat.dats containing the entirety of the code presented in this section plus some testing code.