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.