Infinite list of Fibonacci numbers in Haskell

Viewed 164

Sorry for a question like this. I'm a very beginner programmer, and I'm just started to learn about Haskell. I recently ran into an exercise to implement a function in Haskell that returns an infinite list of Fibonacci numbers. The following code was the answer to the exercise:

fibs :: [Int]
fibs = fibs2 0
 where
    fibs2 :: Int -> [Int]
    fibs2 x = (fib2) x : (fibs2 (x+1))

Can someone explain to me why we should declare another function (fibs2) here and what "where" does in this case?

1 Answers

Can someone explain to me why we should declare another function (fibs2) here?

You certainly aren't obligated to declare another function. However, this particular pattern is quite common. Think of it a bit like loop initialization in other languages. If you want to iterate some process, the easiest way to do that is to write a function that takes some information describing where you are in the iteration, does one step of the "loop", then calls itself with a suitably modified description. For example, if you wanted to sum up all the numbers from 0 to n, you might write:

sumTo :: Int -> Int
sumTo 0 = 0
sumTo n = n + foo (n-1)

BUT frequently the function or value you want is actually the one that starts at a specific value. It's annoying to force all callers of your loop to specify that starting value; and the fact that you've implemented your loop as a recursive function with an argument is an implementation detail they shouldn't have to worry about anyway. So what to do? Well, you define something that calls the loop with the right starting value.

gauss :: Int
gauss = sumTo 100

This way, users can just use gauss and not have to know that 100 is the right starting value for your internal function.

Can someone explain to me what "where" does in this case?

Well, there's one more thing that's a bit unfortunate about our previous sumTo/gauss values: we aren't really interested in sumTo itself, only in gauss, and the fact that it's visible outside of gauss is a violation of an abstraction barrier! If it's easy to call, it may be that somebody else tries to use it; then, if we need to change it to improve what gauss does, we are improving gauss but potentially breaking what that other user is using sumTo for. So we'd like to hide its existence.

That is the purpose of where here: it allows you to define a new thing that's accessible only locally. So:

gauss :: Int
gauss = sumTo 100 where
    sumTo 0 = 0
    sumTo n = n + sumTo (n-1)

In this variant, gauss can be called, but outside of the implementation of gauss, it isn't possible to call sumTo, maintaining a nice abstraction boundary.

Related