Fun with fixed points

haskell

If you import Data.Function, you’ll find that the function fix has a funny type:

fix :: (a -> a) -> a

Given a function f, the value fix f will be “the” fixed point of f. I think this means the least fixed point, which for some reason is supposed to correspond to the greatest fixed point in Haskell, but I’ve still not quite understood enough domain theory to explain why. If \(f\) is a strict function then \(f \bot = \bot\), so least fixed points of strict functions are easy to understand.

The definition of fix looks like this:

fix f = let x = f x in x

So in some sense we start with some x (which one?) and apply f infinitely many times to it. This does not give us a terribly good operational understanding of how we can compute this thing. In fact, it is easier to understand fix f in terms of its key property:

fix f = f (fix f)

In words, applying f to its own (least) fixed point is going to give you back that point! Now suppose we start with some lazy f like const 1, the constant function \(\lambda x. x \to 1\). Then

fix (const 1) = (const 1) (fix (const 1))
              = 1

thanks to lazy evaluation, so we have a fixed point that isn’t \(\bot\). Yay!

Looking at the equation above again, we see that we won’t be able to make progress with computing fix f unless f can be at least partially defined without referring back to fix f. This suggests the following example:

let fibs = fix (\p -> 1 : 1 : zipWith (+) p (tail p))

This is the infinite list of fibonacci numbers, usually written without using fix. The function we use takes it own fixed point as an argument, and then provides some partial information about its value before tying the knot by using its fixed point again. Given a list of integers, this function produces a new list of integers starting with 1, 1. Plugging any list starting with 1, 1 back into the function yields a list starting with 1, 1, 2, because we know how to compute at least the next term at this point. At each step, we learn a bit more information about the fixed point p, and we find that it is the fibonacci sequence itself. This looks like a kind of corecursion.

It’s slightly more surprising that we can also use this trick to implement certain types of recursion. I’ll use the other example, which is the factorial function:

let fact = fix (\f n -> if n == 0 then 1 else n * f (n-1))

The argument to fix in this case is a function of type

(Integer -> Integer) -> (Integer -> Integer)

so the fixed point will be a function rather than just a value! Of course, all functions are just values inhabiting appropriate function spaces, so it should not be so surprising that we can do this. Again, we start with some partial information about the fixed point (its behavior at 0 in this case), and then “pump” it for more information as we need it. The underlying function

g = \f n -> if n == 0 then 1 else n * f (n - 1)

is not strict, since

g undefined = \n -> if n == 0 then 1 else undefined

which is a perfectly good non-\(\bot\) function! There is clearly a whole world of interesting behavior outside of strict functions.

Now: what if we did this at the type level rather than the term level? Then we are talking about fixed points of type constructors… hmm!