Chapter 2. Recursive Functions (1)

Recursion, which literally means running back, plays a pivotal role in problem-solving. For instance, a problem-solving strategy based on divide-and-conquer divides a given problem (that cannot be solved immediately) into subproblems of a similar nature so that the same strategy can be applied to these subproblems recursively; the obtained solutions to these subproblems are then combined in some manner to form a solution to the original problem.

As the first example, let us implement the factorial function that takes a natural number n and returns the product of the first n positive integers. In the following code, a function of the name fact is defined recursively:

// fun fact(n: int): int = if n > 0 then n * fact(n-1) else 1 //

The keyword fun initiates the definition of a function, and the function header following fun specifies that fact is a function taking an integer as its argument and returning another integer as its result. For testing fact, let us include the following line:

val () = println! ("fact(10) = ", fact(10))

where println! is a function-like in ATS, which resembles a function but is not actually a function. In this case, println! can be thought of as a macro that calls a print function on each of its arguments and then prints out a newline at the end. Please do not forget the symbol ! in the name println!.

Suppose we need to call fact on all of the natural numbers less than a given one (e.g., 100). We can first define a function testfact as follows and then call testfact on the given natural number:

// fun testfact (n: int): void = if n > 0 then ( testfact(n-1); println! ("fact(", n-1, ") = ", n-1) ) (* end of [if] *) //

The function header for testfact indicates that testfact takes an integer as its arguments and returns a void-value (of the type void). Often a function is said to return no value if its return is a void-value. Note that the semicolon symbol is for sequencing; a sequence of expressions separated by semicolons are evaluated from left to right and the value returned from evaluating the last expression is taken as the value from evaluating the sequence.

In imperative programming, a function like testfact is normally implemented in terms of a for-loop (instead of being defined recursively). While there is indeed direct support for for-loops and while-loops in ATS, I will not attempt to make use of the support in this book. I hope to make a convincing argument that making extensive use of recursion is a key to increasing one's programming producivity. In fact, I think that a functional programmer should develop a reflex for solving problems recursively.

As another example, the following function fibo is defined to compute Fibonacci numbers:

// fun fibo(n: int): int = if n > 2 then fibo(n-1)+fibo(n-2) else 1 //

The first and the second Fibonacci numbers are 1. Given a positive integer n greater than 2, the nth Fibonacci number equals the sum of the previous two Fibonacci numbers. This implementation of fibo is of expontial time complexity, and it is probably impractical to call it on any integer that is 50 or greater.

For a slightly more interesting example, please study the code in multable.dats, which can be executed to generate the html file multable.html for displaying a multiplication table.

Please find on-line the entirety the code used in this chapter. The mentioned URL link(s) can be found as follows: