Quantcast
Channel: [In]effective Theory
Viewing all articles
Browse latest Browse all 53

The Simplest Recursive Function

$
0
0

GHC's Data.Function package provides an interesting function fix, defined as:

fix f = f $ fix f

Wait, what? This thing's supposed to be useful?

The purpose of this function (a "fixed-point combinator") is to abstract away recursion, and therefore allow us evaluate a recursive function defined non-recursively. In other words, it allows us to express a recursive function (i.e., a function that refers to itself) without ever giving that function a name. Here's how: let's say we have our classic recursive function calculating factorials:

factorial x = case x of 0 -> 1
                        n -> n * factorial (n-1)

If we want to express this function anonymously (and therefore non-recursively), we'd want to write:

\x -> case x of 0 -> 1
                n -> n * factorial (n-1)

But then, factorial is undefined. So what we do is change factorial to an argument f, leaving the task of figuring out what f is to the caller:

anon_factorial :: (Int -> Int) -> Int -> Int
anon_factorial = \f x -> case x of 0 -> 1
                                   n -> n * f (n-1)

Note from the type definition that the factorial function is extracted by applying anon_factorial to the factorial function. This is a perfect application of recursion - fixed_factorial is our new factorial function:

fixed_factorial :: Int -> Int
fixed_factorial = anon_factorial fixed_factorial

This expands trivially to the recursive definition of factorial given above.

The elegance of the solution is that, although we still had to use recursion in the end, we separated that "dirty" task out from the actual work of calculating the factorial function. Of course, we can do a bit better, and generalize fixed_factorial to a function fix which takes any recursive function defined as anon_factorial function was, and converts it to a conventionally-callable function:

fix f = f $ fix f

This function is important not because it's particularly useful (it's not), but because by allowing recursive functions to be defined non-recursively, it encapsulates the very essence of recursion, and nothing else. In a sense, it is the simplest non-trivial recursive function.

Note on Laziness

The fixed-point combinator defined here couldn't actually be evaluated with a special property of Haskell called laziness. Python, for instance, would see

def fix(f, x):
    return f(fix(f, x))

and reduce fix(anon_factorial, 5) to anon_factorial(fix(anon_factorial, 5)). At that point python thinks the next thing to evaluate is the inner expression: fix(anon_factorial, 5). And so we hit infinite recusion.

Haskell, however, will not attempt to simplify until it absolutely needs to. Thus, fix anon_factorial will remain so, until it is applied to a value. So (fix anon_factorial) 5 will expand to anon_factorial (fix anon_factorial) 5, at which point haskell will expand the outer anon_factorial, rather than the inner one. That makes all the difference:

(fix anon_factorial) 5
anon_factorial (fix anon_factorial) 5
case 5 of 0 -> 1
          n -> n * (fix anon_factorial) (5-1)
5 * (fix anon_factorial) 4

And so progress is actually made.


Viewing all articles
Browse latest Browse all 53

Trending Articles