Recursion With Fibonacci

Recursion refers to the property of a function to be defined in term of itself. The Fibonacci sequence is a great example of a recursive problem where a Fibonacci number is calculated from a combination of precedent Fibonacci numbers. Recursion can be implemented in many forms, it is even possible to implement recursion without explicit self calling.
Today we will look at different implementations of Fibonacci and discover their properties.

Each Fibonacci number is calculated by taking the previous Fibonacci number and adding it to the previous-previous Fibonacci number. With initial values for 0 and 1 being f(0)=0 and f(1)=1.
With this definition, we can compute the following sequence:

Before looking into recursion, we’ll look first at how we can represent Fibonacci using a for loop and mutable variables, also know as an Iterative approach.

A for loop in Racket is represented with (for (for-clause ...) body-or-break ... body). The for-clause can be given as a sequence[id seq-expr] or as a number. If a number is provided, it is interpreted as (in-range n). Armed with our knowledge of for loop, we can represent our first iterative version of Fibonacci fibonacci-0:

Given a number n, we immediately return 0 for n=0. Else we define mutable variables a and b with initial values respectively set to 0 for f(0) and 1 for f(1). Next we enter a for loop going from 0 to n-1. At each beginning of loop’s iteration, a represents f(iteration-2) and b represents f(iteration-1). To compute f(iteration), we swap a with b and set b to b + tmp where tmp is the previous value of a (prior being set to b). At the end of the loop, we return b which contains f(n).

If we run through a compilation of n=0, n=1 and n=2, we get the following:

n=0, 0 is directly returned from the first condition;

n=1, a and b is set to 0 and 1, the loop is never entered as n-1 = 1-1 = 0 and b=1 is returned;

n=2, the loop is iterated one time where b is set to tmp+b = 1+1 = 2 and b=2 is returned.

In the same way, we can see how the iteration will calculate the Fibonacci numbers by replacing in turn a and b.

Recursion occurs when a procedure is defined in term of itself. Fibonacci is inherently recursive, f(n) is the result of the sum between the previous Fiboonacci number f(n-1) and the previous-prevous number f(n-2). The procedure computing Fibonacci number can be described identically to its equation:

The result of each recursive call results in another recursive call until an exit condition is met stopping the recursion.
The substitution of the recursive approach highlights a pymarid shape. In Racket, the shape of a function can be observed using trace from (require racket/trace). trace traces the last call of an expression, also known as the Tail call. In this example, the tail call is the arithmetic + procedure occuring once all fibonacci-1 calls have been subtituted with primitive values.

Recursion can also be achieved using an aggregator. An aggregator is a value containing the result of each recursive call and passed as input to the next recursive call until a predicate is reached in which instance the aggregator is returned as final result. This method is sometime called aggregator passing style (aps).

For example with Fibonacci, we need to aggregate f(n-1) and f(n-2) as they will be the running values needed for the next computation.

fibonacci-aps takes a and b as extra parameters to keep track of f(n-1) and f(n-2). Instead of using n as the Fibonacci number, we use n as a iteration counter and compute f(n) by adding a + b and f(n-1) by passing b.

To respect the same signature as fibonacci-1, we partially apply fibonacci-aps with initial parameters 0 and 1.

1

(definefibonacci-2(λ(n)(fibonacci-aps01n)))

If we run through a compilation of n=0, n=1 and n=4, we get the following:

At each tail call, the next recursive is a call with aggregators passed. In comparison to the previous recursive definition fibonacci-1 where each tail call needed expansion of parameters involving recursive calls, in aggregator passing style, the parameters are all primitive values and the tail call is a call to itself. This property is known as Tail recursion.

An interesting aspect of the difference between fibonacci-1 and fibonacci-2 is their shapes. If we trace the fibonacci-aps, we get a linear shape compared to fibonacci-1 which was a pyramid shape.

Some compilers take advantage of this property to represent recursive calls in constant space by replacing each stack space with the latest as each iteration of fibonacci-aps does not require knowledge of prior iterations. This implementation is known as Tail call optimization or also Tail call eliminitation.

So far we provided the identity function (λ (x) x) as a continuation of fibonacci-1 (- n 1) and fibonacci-1 (- n 2) in order for the procedure to be compilable but it was sort of a lie. Identity is surely not the continuation of the two operations.

If we rearrange the operations in separate bindings we would get the following:

Lastly we can introduce recursion without the procedure calling itself. This method can be achieve using the Y Combinator also known as Fixed-point Combinator.
The fixed-point (shortened fixpoint) of a function is defined as x=f(x). It is said that x is the fixpoint of f.

The Y combinator is a procedure taking a function as parameter and returning its fixpoint as result. f=(Y F) where f is the fixpoint of F such as f=F(f). Here is the definition of Y:

1
2
3
4

(defineY(λ(f)((λ(x)(xx))(λ(x)(f(λ(y)((xx)y)))))))

f=(Y F) means that if we define F, a function having as fixpoint a Fibonacci function, we can use Y to find the Fibonacci function itself.

We start first from our initial Fibonacci and instead of the recursion, we call f, a function representing a Fibonacci function taken as parameter.

Next we can use F-fibonacci to create a Fibonacci function with (Y F-fibonacci):

1

(definefibonacci-4(YF-fibonacci))

We now have a working Fibonacci procedure without recursion.

1
2

>(fibonacci-44)3

And that concludes today’s post.

Conclusion

Today we saw different ways of implementing a Fibonacci sequence. We started by looking at an iterative way with loops, then we moved to a recursive approach yielding a pyramid shape, then we moved to a tail recursive approach using aggregators, and we looked at a way to produce tail recursion out of a recursive approach by using continuation passing style. Finally, we completed the post by looking at how the Y combinator could be used to bring recursion to a function without it calling itself. I hope you liked this post and I’ll see you on the next one!