Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:
sub fibonacci { my ( $pop ) = @_; if ($pop < 1) { return 1; } else { fibonacci($pop - 1) + fibonacci($pop - 2); } }
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Puzzled by Recursion. (comparing basic vs tail recursion)
by bobf (Monsignor) on Apr 01, 2005 at 08:40 UTC | |
Others have explained the concept of recursion, but I'd like to mention something about tail recursion, which is slightly different from what you have listed above. (Let me preface this by saying I'm not an expert at algorithms, so I might not be as precise with the wording as I should be.) In your original sub, the first call to the sub can't return a value until two more calls are made (for fibonacci($pop-1) + fibonacci($pop-2)), and each of those subs can't return until two more calls are made, etc. If $pop is large, the stack will fill up with many sub calls waiting for return values. In addition, a lot of extra work will be done because fibonacci(n)(and subsequently fibonacci(n-1), fibonacci(n-2), etc) will be called repetitively for every value of $pop greater than or equal to n. In tail recursion, extra variables are used to retain intermediate values, so the subs on the stack are kept to a minimum and there isn't any wasted computation. To illustrate the difference, here is a comparison between the number of sub calls using TedPride's correction of your original algorithm, and a tail recursive algorithm (which I'm sure could be optimized). The code:
Here is the output. Notice the dramatic difference in the number of sub calls required by the non-tail-recursive algorithm when $pop gets large.
The difference between basic recursion and tail recursion is barely noticable at small values, but the extra time it takes to code a tail-recursive algorithm pays off quickly when the input values get even a little larger. Try using an input value of 50 and you'll see what I mean. HTH Update: The code I gave above was translated from an example in "Mastering Algorithms with C" (O'Reilly), so it was not specifically a perl example. tilly was kind enough to clarify his reply to this post for me in the cb. The words that follow are more his than mine, but it helped me understand things better so I decided to add it to this node. Tail recursion refers to automatically eliminating unnecessary call-frames. It is possible in Perl, but not automatically. You have to use goto &name; to do it, and in many versions of Perl it will leak a bit of memory (defeating the purpose).Thank you for the clarification, tilly! | [reply] [d/l] [select] |
by polettix (Vicar) on Apr 01, 2005 at 11:29 UTC | |
Flavio (perl -e "print(scalar(reverse('ti.xittelop@oivalf')))") Don't fool yourself. | [reply] |
by tilly (Archbishop) on Jun 10, 2005 at 02:40 UTC | |
For instance consider the following to see the effect: This is a recursive function that logically acts like a loop. But unlike a loop, calling it in Perl ties up memory with every function call, so doing a million iteration takes up both time and memory. But in a language like Scheme that has tail recursion, the language recognizes that it doesn't need the information on the intermediate function calls, and eliminates them. (This is how loops are internally implemented in Scheme!) So isn't this optimization always a good thing? Well, not completely. In Perl, for instance, we have a caller function. This can be very useful for debugging. But in Scheme it would be impossible to implement because there is no information about calls that have been cleaned up already. (OK, so you can do the same with goto in Perl, but people don't use that very much so debugging information tends to be there when you want to create a stack backtrace.) | [reply] [d/l] |
|
Re: Puzzled by Recursion.
by TedPride (Priest) on Apr 01, 2005 at 07:22 UTC | |
The function could be more simply (and correctly, since the function specified didn't give the right values) written as: Or even: Where the number returned is the nth number in the fibonacci sequence, n being the number passed to the function: 1 1 2 3 5 8 13 etc. Let's say you call the function with 4:
(4) Is 4 < 3? No. Return (4-1) + (4-2). See, every number in the sequence is built off the previous two values, each of which is built off the two values previous to it, and so on - all the way back to the first and second values (1 and 2 are < 3), which are 1. If the value passed to the function is 1 or 2, it knows to return 1; otherwise it calls itself twice to determine the previous two numbers in the sequence. | [reply] [d/l] [select] |
|
Re: Puzzled by Recursion.
by rupesh (Hermit) on Apr 01, 2005 at 06:18 UTC | |
It would bring some light on the recursion technique(s) in perl. Cheers, Rupesh. | [reply] |
|
Re: Puzzled by Recursion.
by chas (Priest) on Apr 01, 2005 at 06:14 UTC | |
chas (I shortened fibonacci to fib to save space. It does seem rather clever that the parser is able to handle functions that call themselves. I think compilers often implement the recursion in terms of loops; I don't know what Perl does.) | [reply] |
|
Re: Puzzled by Recursion.
by Crackers2 (Parson) on Apr 01, 2005 at 13:12 UTC | |
For me, the thing in code like this that still makes me look at it twice, is the implicit return. The result of the last expression evaluated in a sub is used as the return value if there's no explicit return. I'm not looking to start yet another war here between people who like explicit returns and people who don't. Just wanted to point it out in case that's what the OP found confusing about the else clause, since noone seems to have mentioned it yet. | [reply] |