Документация
HTML CSS PHP PERL другое

Function Footnotes

 
Previous Table of Contents Next

Function Footnotes

Now that you know about scope, there are some things that can be done effectively only with scope. One is recursive subroutines, and the other is the use strict Perl statement that enables a stricter Perl, possibly preventing you from making mistakes.

Declaring Variables local

Perl version 4 didn't have private variables. Instead, Perl 4 had variables that were "almost private." This concept of almost-private variables is still around in Perl 5. You declare these variables by using the local operator, as in this example:


sub myfunc {

    local($foo)= 56;

    # rest of function...

}


In the preceding snippet, $foo is declared to be local to the myfunc() subroutine. A variable that has been declared with local acts almost identically to one declared with my: It can be scoped to a subroutine, block, or eval, and its value is destroyed upon leaving the subroutine or block.

The difference is that a variable declared local can be seen within its scoped block and within any subroutines called from that block. Table 8.1 shows a side-by-side comparison.

Table 8.1. Comparing my and local

sub mess_with_foo {

    $foo=0;

}

sub myfunc {

    my $foo=20;

    mess_with_foo();

    print $foo;

}

myfunc();



sub mess_with_foo {

    $foo=0;

}

sub myfunc {

    local $foo=20;

    mess_with_foo();

    print $foo;

}

myfunc();



The two snippets shown in Table 8.1 are identical except for the declaration of $foo in myfunc(). On the left, it's declared with my; on the right, with local.

When the code on the left runs, $foo is created private to myfunc(). When mess_with_foo() is called, the $foo that's changed inside mess_with_foo() is the global variable $foo. When control returns to myfunc(), the value of 20 is printed, because the $foo in myfunc() was never changed.

When the code on the right runs, $foo is created and declared local to myfunc(). When mess_with_foo() is called, $foo is set to 0. That $foo is the same $foo from myfunc(); the "privateness" is passed down to the called subroutine. Upon returning to myfunc(), the value of 0 is printed.

By the Way

If you're picky about terminology, local variables are technically known as dynamically scoped variables, because their scope changes depending on what subroutines are called. Variables declared with my are called lexically scoped variables, because their scope can be determined by simply reading the code and noting what block they were declared in, and the scope does not change.


Whenever your program needs a variable that's private to a subroutine, you almost always want a variable declared with my.

Making a Stricter Perl

Perl is a permissive language. It doesn't try to get in your way; it allows you to just get your work done without complaining too much about what your code looks like. However, you can also tell Perl to be a bit more strict about your code. For example, Perl can help you avoid silly mistakes if you use the warning switch on the command line—or on the #! line. Perl warns you when you're using undefined variables, using a variable name only once, and so on.

In large software projects, and as your own programs get larger and larger, it's nice to have Perl help you keep yourself in line. In addition to using the -w switch, you can tell the Perl interpreter at compile time to turn on more warnings. You do so by using use strict:


use strict;

sub mysub {

    my $x;

    :

}

mysub();


The use strict statement is actually something called a compiler directive. It tells Perl to flag the following situations as runtime errors that point onward in the current block or file:

  • Attempts to use variable names (other than special variables) that are not declared with my

  • Attempts to use a bare word as a function name when the function definition hasn't been seen yet

  • Other potential errors

The use strict directive, for now, helps you avoid the last two problems. Having Perl flag variables not declared with my prevents you from using a global variable when you actually intend to use a private variable; it's a way of helping you write more self-contained code and not rely on global variables.

The last trap that use strict catches is that of bare keywords. Examine this code:


$var=value;


In this case, do you mean for value to be interpreted as a subroutine call, as a string (but you forgot the quotes), or as a variable (but you forgot the type identifier)? Perl's use strict directive would note that this code is ambiguous and disallow the syntax, unless the subroutine value was already declared before this statement was reached.

From this point on, I will include use strict in all the exercises and longer program listings in this book.

Recursion

You will probably run into a special class of subroutines sooner or later. These subroutines actually call themselves in order to do their work. They are called recursive subroutines.

Recursive subroutines are used wherever tasks can be broken down into smaller and smaller identical tasks. One example of a recursive task is searching a directory tree for a file. After searching the topmost directory, as subdirectories are found, those directories must be searched. And within those directories, if subdirectories are found, those directories must be searched. You should begin to see a pattern here.

Another recursive task is computing factorials, which are frequently used in statistics. For example, the number of ways the six letters ABCDEF can be arranged is 6-factorial, written 6! in mathematics. The factorial of an integer is the product of that integer and all the smaller integers down to 1. So the factorial of 6 is 6x5x4x3x2x1, or 720, and the factorial of 5 is 5x4x3x2x1, or 120. Notice that 6! is equal to 6x5!. So one way to compute the factorial of 6 is to compute the factorial of 5 and multiply that by 6. To compute the factorial of 5, you compute the factorial of 4 and multiply that by 5, and so on. The factorial of 1, on the other hand, is just 1 by the definition. The factorial of 0 is also defined as 1, so that 1! equals 1x0! regularly. These facts lead us to a recursive subroutine for computing factorials, which is shown in Listing 8.3.

Listing 8.3. Factorials

1:   sub factorial {

2:      my ($num)=@_;

3:      return(1) if ($num <= 1);

4:      return($num * factorial($num - 1));

5:   }

6:   print factorial(6);


Line 2: The argument to the factorial() subroutine is copied to $num, which is declared private to the subroutine.

Line 3: Every recursive subroutine needs to have a termination condition. That is, there needs to be some argument value at which the subroutine no longer calls itself for an answer. Otherwise the subroutine would try to call itself infinitely and never return an answer. For the factorial() subroutine, the termination condition is 1-factorial (or 0-factorial); as stated previously, those two values are both 1. The factorial() subroutine will execute the return(1) when called with 1 or 0 ($num<=1).

Line 4: Otherwise, if the argument is not 0 or 1, the factorial of the next smaller sequence must be computed. Observe the following:

If $num is:

Line #4 evaluates as follows:

6

return(6 * factorial(5))

5

return(5 * factorial(4))

4

return(4 * factorial(3))

3

return(3 * factorial(2))

2

return(2 * factorial(1))

1

Line #4 is not reached; factorial(1) returns 1.


After the next-smaller factorials are computed (all the way down to 1), the subroutine starts returning values up through the chain of calls until finally 6-factorial can be computed.

Recursive subroutines aren't very common. Large ones are complicated to construct and difficult to debug. Any task that can be done through iteration (using for, while, foreach) can be done with recursion, and any recursive task can be accomplished iteratively. Recursion is usually reserved for a small set of tasks that lend themselves to it easily.

    Previous Table of Contents Next
    © 2000- NIV