As others pointed out, fix
somehow captures the essence of recursion. Other answers already do a great job at explaining how fix
works. So let's take a look from another angle and see how fix
can be derived by generalising, starting from a specific problem: we want to implement the factorial function.
Let's first define a non recursive factorial function. We will use it later to "bootstrap" our recursive implementation.
factorial n = product [1..n]
We approximate the factorial function by a sequence of functions: for each natural number n
, factorial_n
coincides with factorial
up to and including n
. Clearly factorial_n
converges towards factorial
for n
going towards infinity.
factorial_0 n = if n == 0 then 1 else undefined
factorial_1 n = n * factorial_0(n - 1)
factorial_2 n = n * factorial_1(n - 1)
factorial_3 n = n * factorial_2(n - 1)
...
Instead of writing factorial_n
out by hand for every n
, we implement a factory function that creates these for us. We do this by factoring the commonalities out and abstracting over the calls to factorial_[n - 1]
by making them a parameter to the factory function.
factorialMaker f n = if n == 0 then 1 else n * f(n - 1)
Using this factory, we can create the same converging sequence of functions as above. For each factorial_n
we need to pass a function that calculates the factorials up to n - 1
. We simply use the factorial_[n - 1]
from the previous step.
factorial_0 = factorialMaker undefined
factorial_1 = factorialMaker factorial_0
factorial_2 = factorialMaker factorial_1
factorial_3 = factorialMaker factorial_2
...
If we pass our real factorial function instead, we materialize the limit of the series.
factorial_inf = factorialMaker factorial
But since that limit is the real factorial function we have factorial = factorial_inf
and thus
factorial = factorialMaker factorial
Which means that factorial
is a fixed-point of factorialMaker
.
Finally we derive the function fix
, which returns factorial
given factorialMaker
. We do this by abstracting over factorialMaker
and make it an argument to fix
. (i.e. f
corresponds to factorialMaker
and fix f
to factorial
):
fix f = f (fix f)
Now we find factorial
by calculating the fixed-point of factorialMaker
.
factorial = fix factorialMaker
fix error
in ghci and feel good about yourself." – Reciprocationfix
asfix f = f (fix f)
. Short, simple, works, and identical to the mathematical definition. – Owenowenafix (1:) !! (10^8)
. The original does it in constant memory, yours takes linear memory (which makes it quite a bit slower, too). That is, using the let "ties a tighter knot", and allows a circular data structure to be generated, whereas yours does not. – Diagnosticsfix
too! helped me understandfix
a lot. – Eloyelreath