3

The inefficient (tree-recursive) fib(n) function calculates the n-th Fibonacci number. In Haskell:

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

Using the following identity:

gfib(n) = (fib(n), fib(n+1))

we could synthesize a linear recursive version:

fib n = fst (gfib n)
    where gfib 0 = (0, 1)
          gfib n = (b, a + b)
              where (a, b) = gfib (n - 1)

On the other hand, there's a well-known tail-recursive version:

fib n = go n 0 1
    where go 0 a b = a
          go n a b = go (n - 1) b (a + b)

Now the question:

Is there a way to synthesize the tail-recursive version from the linear recursive one using the Burstall & Darlington's folding/unfolding technique? My goal is to understand how can I transform the lineal recursive program so the returning tuple were converted to two accumulating parameters so the resulting program were tail-recursive.

Context: functional programming, program synthesis/derivation, program transformation, induction

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Ricardo Pérez
  • 215
  • 1
  • 8
  • I don't know the B&D technique, but I believe that recursions of the form `g n = f (g (n-1))` (and a base case) can be transformed using an accumulator `go n x = go (n-1) (f x)`. At the end of the day, we are computing `f . f . f . ... $ base` in both cases, one starting from the left, the other one starting the the right. – chi Jul 27 '22 at 11:47

1 Answers1

2

That transformation is called "accumulating" (e.g. in Algorithm Design in Haskell by Bird and Gibbons).

fib n = fst (go n)
    where go 0 = (0, 1)
          go n = (b, a + b)
              where (a, b) = go (n - 1)

Accumulating:

fib n = fst (go n (0, 1))
    where go 0 (a, b) = (a, b)
          go n (a, b) = go (n - 1) (b, a + b)

Although it should be noted that in this case the accumulation is easy because it doesn't matter if you count up or down (n is not really used). But in general you should take care that the resulting function is still correct.

Then to get to your desired implementation you have to apply two more simple transformations:

Push in fst (I don't know if that's a common name):

fib n = go n (0, 1)
    where go 0 (a, b) = a
          go n (a, b) = go (n - 1) (b, a + b)

Currying:

fib n = go n 0 1
    where go 0 a b = a
          go n a b = go (n - 1) b (a + b)
Noughtmare
  • 9,410
  • 1
  • 12
  • 38
  • Thank you, @noughtmare. That's not exactly what I asked for (because the solution was not achieved using the folding/unfolding technique) but it helped me a lot to understand how can I go from one version to the other. – Ricardo Pérez Jul 27 '22 at 12:58