What is “fix” in Haskell? And why does “fix error” print an infinite string? And why also “take 10 $ fix error” does the same too?

fix calculates a fixed point of a function; a fixed point is a value you can feed to the function for which it will produce exactly the same value as the result.

For example, if you have the function f _ = "hello" (or const "hello"), then a fixed point of this function is the string "hello". And indeed fix f is "hello".

Many functions have multiple fixed points, so the documentation for fix needs to specify which one it returns. For example:

g :: Integer -> Integer
g x
  | even x = x
  | otherwise = x + 1

Every even number is a fixed point of g, but fix g promises (by its type) to be a specific Integer. Which one?

The documentation says that fix produces the least fixed point, and further clarifies that this means the least defined value that is a fixed point of the input function. Note that “least defined” does not mean the smallest value that is defined, it means the value that has the minimum “definedness”. This is a technical area I’m not quite as on top of as I’d like to be, but I can at least give you some informal intuition of what it means1: values like 1 :: Integer, True, (), Just 'a' etc a fully defined, because you can evaluate them fully without hitting an error. Bottom values (undefined, let x = x in x, etc) are completely undefined. In-between are values like 1 : 2 : undefined, where you can look at some structure without hitting an error, but there is a bottom somewhere inside. So if bottom is a possibility, it’s always the least defined possibility.

So fix g is just bottom (GHC detected an infinite loop and aborted it, when I tried), because g undefined is an error (and all bottoms are “the same value” for this purpose).

This turns out to be the case for most simple functions you might write when playing around with fix. For any strict function (one which examines its argument in any way), bottom will be a fixed point and that’s the one fix will calculate. So it can seem pretty useless when you’re just messing around trying to see what it does. So why do we care about it?

fix is of great theoretical interest because you can use it to implement recursion in a language that lacks direct support for it. In recursive definitions like this:

sum :: [Integer] -> Integer
sum [] = 0
sum (x : xs) = x + sum xs

There’s actually something slightly impressive going on. You’re defining sum, but sum is in scope within its own definition. Implementing that feature is a bit more difficult than compiling definitions which only use pre-existing definitions. Let’s imagine we couldn’t do that, but we still want to write sum. You can do it like this:

sum' :: ([Integer] -> Integer) -> [Integer] -> Integer
sum' _ [] = 0
sum' rec (x : xs) = x + rec xs

sum = fix sum'

Now every definition only uses things that have previously been defined; there is no self reference. Instead of calling itself directly sum' receives an extra argument which is the function it should call on the tail of the list. That function argument has to have the same type we originally wanted to give sum, which makes the type of sum' an instance of a -> a, so we can call fix on it. And it turns out the least fixed point of sum' is a function equivalent to the original sum!

The way it does this is by generating an “infinitely nested” expression of the form sum' (sum' (sum' (sum' ...))) (this is basically the way it finds the fixed point of any function; the answer is already getting pretty long so I won’t go over why exactly this works here). Each sum' receives an argument that says what to do with the tail of the list; that argument is itself another call to sum' that needs an argument saying what to do with the tail of the tail of the original list, and that argument is another call to sum', and so on. Eventually (if the list is finite) we hit the base case for the empty list and don’t need the next level of nested sum' call, and thus it doesn’t matter that there is no end to the nested expression! (This wouldn’t work in an eagerly evaluated language, obviously)

This turns out to be a general pattern you can apply to translate direct recursion into usage of fix.

So with that all said, hopefully you can see why fix error behaves the way it does. Willem Van Onsem’s answer is good here, so I won’t repeat at length. But basically fix error has to come up with a string s such that error s is equivalent to s. This is of course impossible for any non-bottom string, since error always produces bottom (that’s the whole point of it), so fix error has to be some form of bottom. In searching for the fixed point it generates an infinitely nested expression error (error (error ...)), and when GHC is printing an error message which itself generates an error whose message is another error, etc etc, what you have seen is the output that gets produced.


1 This concept of “least defined” comes from domain theory. If you would like to know more, a couple of links were suggested in the comments:

  • Lecture notes from a beginner’s course in domain theory: https://www.cs.nott.ac.uk/~pszgmh/domains.html
  • The Wikibooks article on denotational semantics in Haskell isn’t specifically about domain theory, but does go over this specific concept in much more detail than I have: https://en.m.wikibooks.org/wiki/Haskell/Denotational_semantics

Leave a Comment