The canonical function to demonstrate recursion is the factorial() function. I tried a simple implementation of it myself and came up with this:
factorial <- function(x){
if(x==1)
return( 1)
else
return(x*factorial(x-1))
}
From my survey of the topic, there seems to be some debate about whether it's better to use recursion or simple iteration. I wanted to see how R implements it and found a factorial() function in the gregmisc package. I thought I'd find something like my implementation or instead a regular iteration. What I found this:
> factorial
function (x)
gamma(x + 1)
<environment: namespace:base>
So the answer to my question of whether R prefers recursion or iteration is "neither". At least in this implementation. Are recursive functions avoided in R for good reason?
Update:
gregmisc version:
>ptm <- proc.time()
> factorial(144)
[1] 5.550294e+249
> proc.time() - ptm
user system elapsed
0.001 0.000 0.001
my version:
> factorial(144)
[1] 5.550294e+249
> proc.time() - ptm
user system elapsed
0.002 0.001 0.006
Recursive programming is not a bad practice. It is a tool in your toolbox and like any tool, when it's the only tool used that's when bad things happen. Or when it's used out of a proper context.
Recursive functions can be used to solve tasks in elegant ways. When a function calls itself, that's called a recursion step. The basis of recursion is function arguments that make the task so simple that the function does not make further calls.
First of all, it's counterintuitive. We don't tend to think about things in a recursive way. And then on top of that, unless you're one of the few people out there who uses Haskell or another functional language, you simply never actually use recursion in the real world.
For calculation of integer factorial, the recursive implementation is slower and more complex. Invariably, iteration is used in production code.
The factorial
function you refer to is in the base package. It operates on real values rather than integers, hence that implementation. Its documentation states:
factorial(x) (x! for non-negative integer x) is defined to be gamma(x+1)
A more interesting example is code to implement the Fibonnaci series which is extraordinarily wasteful when implemented with a naive recursion. The recursive approach can be made efficient through memoization but simple iteration is always to be preferred if performance is at stake.
Another common algorithm that is expressed naturally in a recursive way is Quicksort. This can, like all algorithms be implemented without recursion, but is quite complex to do so. There is little benefit in using a non-recursive Quicksort and so it's common to use the naive recursive implementation.
Recursion is a good implementation choice:
I think that the most natural way to express mathematical computations in R is through mathematical/statistical notation. The whole point of the language is to make it easy to express statistical computations in a natural manner.
You own example of factorial
being implementated using gamma
fits nicely with this view. I don't know how gamma
is implemented but we don't need to know that in order to use R. As a user, the most important thing is usually to get the right answer. If the code proves to be prohibitively slow, that is when you optimize. The first place to start would again be math and the choice of algorithm, not implementation details.
David Heffernan is correct that recursion is usually slower than iteration but what he doesn't seem to consider is that it rarely matters. Using his own example, the Fibonacci numbers, the really important thing is to avoid recomputing the numbers, which can be done through memorization. This makes the computation run in linear time instead of exponential time - a huge improvement. Once you have done that, you can still get a minor improvement by implementing the algorithm using a loop but then it probably doesn't matter. Besides, there is a closed form.
Both the factorial function and the fibonacci numbers also grow very quickly. This means that each arithmetic operation (additions, multiplications, etc.) will start to take a long time, while the recursion doesn't become more expensive or at least not as quickly. Again, mathematical considerations trump implementation details.
My advice is:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With