Termination
Learn about the termination of programs.
We'll cover the following
Once we start writing recursive functions, we need to worry about the termination of our programs (i.e. do they return a result in a finite amount of time?
Non-termination
It is possible to write recursive functions that do not terminate.
loop :: Int -> Int
loop x = loop x
Here, the right hand side of the only equation of loop
is identical to its left hand side. So, the evaluation of an expression like loop 1
will make no progress:
loop 1
= loop 1
= loop 1
= ...
If we try to evaluate an expression like loop 1
in ghci
, it will loop forever. You can cancel computations in ghci
by hitting CTRL + C
on the keyboard.
This was, of course, a hypothetical example. But even when writing meaningful recursive functions, we need to be careful in making sure that they always terminate. To achieve this:
- At least one equation of the function must be a non-recursive base case.
- The recursive equations must modify the input argument such that the recursive calls get “closer” to a base case.
Example: compoundInterest
Let’s check if our function compoundInterest
terminates on all inputs. Remember, the definition of the function was:
compoundInterest :: Int -> Double
compoundInterest 0 = 1000
compoundInterest n = 1.05 * compoundInterest (n - 1)
At first it might look like it does always terminate, since
- For the value
0
, the result is1000
, so we have a base case. - For other inputs greater than
0
, we go fromcompoundInterest n
intocompoundInterest (n - 1)
in the recursive equation. So, the value of the function argumentn
will get smaller and smaller in the recursive calls, until it finally reaches the base case0
and the recursion terminates.
However, there is one thing we overlooked here. The type of our function is
compoundInterest :: Int -> Double
So, we can call it on all integers, including negative numbers.
And trying to evaluate compoundInterest (-1)
in ghci
will lead to an infinite loop.
Our function compoundInterest
does not return results on all inputs. Thus, compoundInterest
is a partial function. It differs from the partial functions we have seen so far, though, as the equations of compoundInterest
can match any integer value.
In fact, when we try to evaluate compoundInterest (-1)
, the second equation will match (as the pattern variable n
matches any input value). The first few steps of the evaluation look like this:
compoundInterest (-1)
= 1.05 * compoundInterest (-1 -1)
= 1.05 * compoundInterest (-2)
= 1.05 * 1.05 * compoundInterest (-2 -1)
= 1.05 * 1.05 * compoundInterest (-3)
= 1.05 * 1.05 * 1.05 * compoundInterest (-3 -1)
= ...
and so on.
So, we have found a bug in the compoundInterest
function. How can we fix it? A pragmatic solution could be to simply treat negative inputs as we do the 0
case:
Get hands-on with 1400+ tech skills courses.