Haskell/Fix and recursion

At first glance, the  function may appear odd and useless. However, there is a theoretical reason for its existence: introducing it into the (typed) lambda calculus as a primitive allows you to define recursive functions.

Introducing
is simply defined as:

fix :: (a -> a) -> a fix f = let {x = f x} in x

Doesn't that seem ... magical? You might be wondering: surely  will cause an infinite series of nested applications of  s:  ? The resolution here is lazy evaluation. Essentially, this infinite sequence of applications of  will be avoided if (and only if)   is a lazy function. Let's see some examples:

We first import the  module to bring   (which is also exported by the   module) into scope. Then we try three examples. Let us try to see what actually happens in the first example:

-- fix f = let {x = f x} in x  fix (2+) = let {x = 2 + x} in x = let {x = 2 + x} in 2 + x  = let {x = 2 + x} in 2 + (2 + x)  = let {x = 2 + x} in 2 + (2 + (2 + x)) = let {x = 2 + x} in 2 + (2 + (2 + (2 + x))) = ...

The first example uses  which is an eager function; it needs the value of   before it can compute ... the value of  . Therefore this calculation will never stop. Let us now look at the second example:

fix (const "hello") = let {x = const "hello" x} in x = let {x = const "hello" x} in const "hello" x  = let {x = const "hello" x} in "hello" = "hello"

This is quite different: we note that after a single expansion of, the evaluation quickly terminates, because   ignores its second argument. The evaluation for the last example is a little different, but we can proceed similarly:

fix (1:) = let {x = 1 : x} in x = let {x = 1 : x} in 1 : x

Here  is already in weak head normal form (  is a lazy data constructor), so the expansion stops. This actually creates a cyclic structure. Each time new element of this list is requested by a consumer function, 's definition is consulted, and it is already known to be.

Thus, requesting elements from this list one by one will return s one after another while requested, without limit. So  will produce a list of ten  s, but trying to print them all by typing   into GHCi causes the infinite stream of  s to be printed.

Well actually it causes the evaluation of , and although the   will never complete its work in full, it does produce its output incrementally, piece by piece, one   at a time:

"[" ++ intercalate "," (map show (fix (1:))) ++ "]" = "[" ++ intercalate "," (map show (let {x = 1 : x} in x)) ++ "]" = "[" ++ intercalate "," (map show (let {x = 1 : x} in 1 : x)) ++ "]" = "[" ++ "1" ++ "," ++ intercalate "," (map show (let {x = 1 : x} in x)) ++ "]" = "[1," ++ intercalate "," (map show (let {x = 1 : x} in 1 : x)) ++ "]" = "[1," ++ "1" ++ "," ++ intercalate "," (map show (let {x = 1 : x} in x)) ++ "]" = "[1,1," ++ intercalate "," (map show (let {x = 1 : x} in 1 : x)) ++ "]" = "[1,1," ++ "1" ++ "," ++ intercalate "," (map show (let {x = 1 : x} in x)) ++ "]" = "[1,1,1," ++ intercalate "," (map show (let {x = 1 : x} in 1 : x)) ++ "]" = ...

This is lazy evaluation at work: the printing function doesn't need to consume its entire input string before beginning to print, it starts printing as soon as it can.

Lastly, iteratively calculating an approximation of a square root of a number,

-- fix f = let {x = f x} in x  fix (\next guess tol val -> if abs(guess^2-val) < tol then guess else                      next ((guess + val / guess) / 2.0) tol val) 2.0 0.0001 25.0 = (let {rec = (\next guess tol val -> if abs(guess^2-val) < tol then guess else next ((guess + val / guess) / 2.0) tol val) rec}   in rec)  2.0 0.0001 25.0 = let {rec guess tol val =  if abs(guess^2-val) < tol then guess else rec ((guess + val / guess) / 2.0) tol val} in rec   2.0 0.0001 25.0 = 5.000000000016778

and fixed points
can also be defined in a way that does not introduce structural sharing:

fix :: (a -> a) -> a fix f = -- x        where {x = f x}         -- f x       where {x = fix f}            f (fix f)

A fixed point of a function  is a value   such that. For example,  is a fixed point of the function   since. This is where the name of  comes from: it finds the least-defined fixed point of a function. (We'll come to what "least defined" means in a minute.) For the two aforementioned examples that converge, this is readily seen:

const "hello" "hello" = "hello" (1:) [1,1,..] = [1,1,...]

And since there's no number  such that , it also makes sense that   diverges.

In fact, it's obvious from the definition of  that it finds a fixed point. All we need to do is write the equation for  the other way around:

f (fix f) = fix f

Which is precisely the definition of a fixed point! So it seems that  should always find a fixed point. But sometimes  seems to fail at this, as sometimes it diverges. We can repair this property, however, if we bring in some denotational semantics. Every Haskell type actually includes a special value called bottom, written. So the values with type, for example,  include, in fact,   as well as   etc.. Divergent computations are denoted by a value of, i.e., we have that.

The special value  is also denoted by this. Now we can understand how  finds fixed points of functions like  :

So feeding  (i.e.,  ) to   gives us   back. So  is a fixed point of  !

In the case of, it is the only fixed point. However, there are other functions  with several fixed points for which   still diverges:   diverges, but we remarked above that   is a fixed point of that function. This is where the "least-defined" clause comes in. Types in Haskell have a partial order on them called definedness. In any type,  is the least-defined value (hence the name "bottom"). For simple types like, the only pairs in the partial order are  ,   and so on. We do not have  for any non-bottom  s ,. Similar comments apply to other simple types like  and. For "layered" values such as lists or, the picture is more complicated, and we refer to the chapter on denotational semantics.

So since  is the least-defined value for all types and   finds the least-defined fixed point, if , we will have   (and the converse is also true). If you've read the denotational semantics article, you will recognise this as the criterion for a strict function:  diverges if and only if   is strict.

Recursion
If you have already come across examples of, chances are they were examples involving   and recursion. Here's a classic example:

Here we have used  to "encode" the factorial function: note that (if we regard   as a language primitive) our second definition of   doesn't involve recursion at all. In a language like the typed lambda calculus that doesn't feature recursion, we can introduce  in to write recursive functions in this way. Here are some more examples:

So how does this work? Let's first approach it from a denotational point of view with our  function. For brevity let's define:

fact' rec n = if n == 0 then 1 else n * rec (n-1)

This is the same function as in the first example above, except that we gave a name to the anonymous function so that we're computing  now. will find a fixed point of, i.e. the function   such that. But let's expand what this means:

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

All we did was substitute  for   in the definition of. But this looks exactly like a recursive definition of a factorial function! feeds itself to  as its first parameter thus creating a recursive function out of a higher-order function (a.k.a. a functional, in old time LISP parlance) which expresses one step of the overall recursive calculation.

We can also consider things from a more operational point of view. Let's actually expand the definition of :

fix fact' = fact' (fix fact') = (\rec n -> if n == 0 then 1 else n * rec (n-1)) (fix fact') = \n -> if n == 0 then 1 else n * fix fact' (n-1) = \n -> if n == 0 then 1 else n * fact' (fix fact') (n-1) = \n -> if n == 0 then 1 else n * (\rec n' -> if n' == 0 then 1 else n' * rec (n'-1)) (fix fact') (n-1) = \n -> if n == 0 then 1 else n * (if n-1 == 0 then 1 else (n-1) * fix fact' (n-2)) = \n -> if n == 0 then 1 else n * (if n-1 == 0 then 1                  else (n-1) * (if n-2 == 0 then 1 else (n-2) * fix fact' (n-3))) = ...

Notice that the use of  allows us to keep "unraveling" the definition of  : every time we hit the   clause, we produce another copy of   via the evaluation rule , which functions as the next call in the recursion chain. Eventually we hit the  clause and bottom out of this chain.

And with the sharing definition of  as at the top of this page,   would create an actually recursive function, which uses its own actual self to make the recursive call, instead of its copy:

fix fact' = let rec = fact' rec in rec = let rec = (\rec' n -> if n == 0 then 1 else n * rec' (n-1)) rec in rec = let rec = (\n -> if n == 0 then 1 else n * rec (n-1)) in rec = let rec = (\n -> if n == 0 then 1 else n * rec (n-1)) in    \n -> if n == 0 then 1 else n * rec (n-1) = let rec = (\n -> if n == 0 then 1 else n * rec (n-1)) in    \n -> if n == 0 then 1 else n * (if n-1 == 0 then 1 else (n-1) * rec (n-2)) = ...

This is made possible by the fact that Haskell's  allows recursive definitions, and is actually like Scheme's   in this regard. The non-sharing  definition does not use this and would work with non-recursive   as well -- although it itself is also defined recursively (here), the function it creates is not actually self-referential and achieves recursion by recreating its own copy and calling it instead, as we've seen above. That's why one way to add recursion to a non-recursive language is to add  to it, as a primitive construct.

Non-recursive definitions for  also exist. The simplest and most famous of them is known as Y combinator. In non-typed Haskell it could've been written as  where   is known as U combinator.

The typed lambda calculus
In this section we'll expand upon a point mentioned a few times in the previous section: how  allows us to encode recursion in the typed lambda calculus. It presumes you've already met the typed lambda calculus. Recall that in the lambda calculus, there is no  clause or top-level bindings. Every program is a simple tree of lambda abstractions, applications and literals. Let's say we want to write a  function. Assuming we have a type called  for the natural numbers, we'd start out something like the following:

λn:Nat. if iszero n then 1 else n * (n-1)

The problem is, how do we fill in the ? We don't have a name for our function, so we can't call it recursively. The only way to bind names to terms is to use a lambda abstraction, so let's give that a go:

(λf:Nat→Nat. λn:Nat. if iszero n then 1 else n * f (n-1)) (λm:Nat. if iszero m then 1 else m * (m-1))

This expands to:

λn:Nat. if iszero n then 1 else n * (if iszero n-1 then 1 else (n-1) * (n-2))

We still have a. We could try to add one more layer in: (λf:Nat→Nat. λn:Nat. if iszero n then 1 else n * f (n-1)  ((λg:Nat→Nat. λm:Nat. if iszero m then 1 else m * g (m-1)) (λp:Nat. if iszero p then 1 else p * (p-1)))) -> λn:Nat. if iszero n then 1 else n * (if iszero n-1 then 1                  else (n-1) * (if iszero n-2 then 1 else (n-2) * (n-3)))

It's pretty clear we're never going to be able to get rid of this, no matter how many levels of naming we add in. Never, that is, unless we use, which, in essence, provides an object from which we can always unravel one more layer of recursion and still have what we started off:

fix (λf:Nat→Nat. λn:Nat. if iszero n then 1 else n * f (n-1))

This is a perfect factorial function in the typed lambda calculus plus.

is actually slightly more interesting than that in the context of the typed lambda calculus: if we introduce it into the language, then every type becomes inhabited, because given some concrete type, the following expression has type  :

fix (λx:T. x)

This, in Haskell-speak, is, which is denotationally. So we see that as soon as we introduce  to the typed lambda calculus, the property that every well-typed term reduces to a value is lost.

Fix as a data type
It is also possible to make a fix data type in Haskell.

There are three ways of defining it. newtype Fix f = Fix (f (Fix f))

or using the RankNTypes extension newtype Mu f = Mu (forall a . (f a -> a) -> a) data Nu f = forall a. Nu a (a -> f a)

Mu and Nu help generalize folds, unfolds and refolds. fold :: (f a -> a) -> Mu f -> a fold g (Mu f) = f g unfold :: (a -> f a) -> a -> Nu f unfold f x = Nu x f refold :: (a -> f a) -> (g a -> a) -> Mu f -> Nu g refold f g = unfold g. fold f

Mu and Nu are restricted versions of Fix. Mu is used for making inductive noninfinite data and Nu is used for making coinductive infinite data. Eg) newtype Stream a = Stream (Nu ( a)) -- exists b . (b, b -> (a, b)) newtype Void a = Void (Mu ( a))    -- forall b . ((a, b) -> b) -> b

Unlike the fix point function the fix point types do not lead to bottom. In the following code Bot is perfectly defined. It is equivalent to the unit type. newtype Id a = Id a newtype Bot = Bot (Fix Id) -- equals         newtype Bot=Bot Bot -- There is only one allowable term. Bot $ Bot $ Bot $ Bot ..,

The Fix data type cannot model all forms of recursion. Take for instance this nonregular data type. data Node a = Two a a | Three a a a data FingerTree a = U a | Up (FingerTree (Node a)) It is not easy to implement this using Fix.