[Israel.pm] What does this code do?

Shlomi Fish shlomif at iglu.org.il
Sun May 2 05:56:25 PDT 2004


On Wednesday 28 April 2004 15:57, Srikanth Madani wrote:

> > > General question - is recursion considered good programming practice?
>
> > I'd rather answer it to the list. Can I, or would you rather have a 
> > private answer?
>
> On list is even better!

Well, here goes. 

There are several types of recursion. One of them is tail-recursion. I'll 
exemplify it with the following Perl code that calculates the factorial: 

<<<
#!/usr/bin/perl -w

use strict;

sub fact_helper
{
    my ($n, $i, $prod) = @_;
    if ($i > $n)
    {
        return $prod;
    }
    else
    {
        return fact_helper($n, $i+1, $prod*$i);
    }
}

sub fact
{
    my $n = shift;
    return fact_helper($n, 1, 1);
}

my $n = shift;
print fact($n), "\n";
>>>

As you can see, fact_helper() returns either a constant or another unmodified 
call to itself with different arguments. Thus, what can be done is that the 
return statement would be a "goto" statement to the beginning of the function 
after modifying the arguments. Something like this:

<<<
sub fact_helper
{
    my ($n, $i, $prod) = @_;
fact_helper_start:
    if ($i > $n)
    {
        return $prod;
    }
    else
    {
        $prod *= $i;
        $i++;
        goto fact_helper_start;
    }
}
>>>

Some compilers or interpreters do this optimazation and it is called "proper 
tail recursion". Now, in some languages (like Scheme) this is the standard 
way to perform loops. 

Perl does not have proper-tail-recursion (at least not yet) and so this method 
results in a return flourish at the end, which makes it somewhat in-efficient 
speed-wise and time-wise. Instead this code can be implemented as:

<<<
sub fact
{
    my $n = shift;
    my $prod = 1;

    for(my $i=1;!($i>$n);$i++)
    {
        $prod *= $i;
    }
    return $prod;
}
>>>

Which is essentially the same as the version with the "goto" beforehand, just 
in a better style.

Another type of recursion is linear (non-tail) recursion. This can be 
exemplified in the following function for calculating the factorial function:

<<<
sub fact
{
    my $n = shift;
    if ($n == 0)
    {
        return 1;
    }
    else
    {
        return $n * fact($n-1);
    }
}
>>>

As you can see the type returned is an expression based on the result of 
another invocation of the function. Thus, even if we do have a proper tail 
recursion optimization, it won't help us. 

Finally, there's also tree recursion. This is what was demonstrated in the 
original ({0,1,2}**N) code example. This, and the two other kinds of 
recursion can be simulated without using procedure self-invocation by 
maintaining a stack of results and creating a loop that increments and 
decrements a pointer to the current place the calculation occurs.

You can see some discussion of these recursions, in Scheme, here:

http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-11.html

Now, when to use recursion? Most of the tail-recursion and linear-recursion 
can be implemented as loops. Still, recursion is sometimes used to clearly 
illustrate the algorithm, make the code clearer, or calculate the complexity 
of the algorithm.

As for tree recursion, this can be implemented as procedural recursion for 
clarity. However if the depth is not known to be limited in advance, it is a 
good idea to implement it with a dedicated stack.

Perl will warn you if you have recursed a lot. (100 times if I remember 
correctly) if "use warnings" is specified. There would also be problems in 
debugging. A workaround can be found here:

http://vipe.technion.ac.il/~shlomif/lecture/LM-Solve/slides/exotic-bugs/recursion_limit.html

Regards,

	Shlomi Fish

-- 

---------------------------------------------------------------------
Shlomi Fish      shlomif at iglu.org.il
Homepage:        http://shlomif.il.eu.org/

Quidquid latine dictum sit, altum viditur.
        [Whatever is said in Latin sounds profound.]



More information about the Perl mailing list