Suppose that foo and bar are two mutually defined recursive
functions. In the body of foo or bar, a tail-call to foo or bar is a
mutually recursive tail-call. For instance, the following two functions
isevn and isodd are mutually recursive:
The mutually recursive call to
isodd in the body of
isevn is a tail-call, and the mutually recursive call to
isevn in the body of
isodd is also a tail-call.
If we want that these two tail-calls be compiled into local jumps, we
should replace the keyword
fun with the keyword
fn* as follows:
What the ATS compiler does in this case is to combine these two functions
into a single one so that each mutually recursive tail-call in their bodies
can be turned into a self tail-call, which is then ready to be compiled
into a local jump.
When writing code corresponding to embedded loops in an imperative
programming language such as C or Java, we often need to make sure that
mutually recursive tail-calls are compiled into local jumps. The following
function print_multable is implemented to print out a standard
multiplication table for nonzero digits:
The functions
loop1 and
loop2 are defined
mutually recursively, and the mutually recursive calls in their bodies are
all tail-calls. The keyword
fn* indicates to the ATS compiler
that the functions
loop1 and
loop2 should be
combined so that these tail-calls can be compiled into local jumps. In a
case where
N is a large number (e.g., 1,000,000), calling
loop1 may run the risk of stack overflow if these tail-calls
are not compiled into local jumps.
When called, the function print_multable prints out the
following multiplication table:
In summary, the very ability to turn mutually recursive tail-calls
into local jumps makes it possible to implement embedded loops as mutually
tail-recursive functions. This ability is indispensable for advocating the
practice of replacing loops with recursive functions in ATS.