Полезная информация

Advanced Perl Programming

Advanced Perl ProgrammingSearch this book
Previous: 4.3 ClosuresChapter 4
Subroutine References and Closures
Next: 4.5 Comparisons to Other Languages
 

4.4 Using Closures

Closures are used in two somewhat distinct ways. The most common usage is as "smart" callback procedures. The other idiom is that of "iterators" (or "streams," as they are known in the LISP world).

4.4.1 Using Closures as "Smart" Callbacks

Since closures are subroutine references with a bit of private data thrown in, they are very convenient to use as callback procedures in graphical user interfaces.

Let's say you create a button using the Tk toolkit and give it a subroutine reference. When the button is pressed, it calls this subroutine back. Now if the same subroutine is given to two different buttons on the screen, there's a problem: How does the subroutine know which button is calling it? Simple. Instead of giving the button a reference to an ordinary subroutine, you give it a "smart" callback subroutine - a closure. This closure stores away some data specific to a button (such as its name), and when the subroutine is called, it magically has access to that data, as shown in Example 4.2.

This example creates two buttons that when clicked, print out their title strings. Though the discussion about packages and, specifically, the Tk module is slated for chapters to come, you might still understand the gist of the code in Example 4.2. For the moment, pay attention only to the part that uses closures (highlighted in boldface) and not to the mechanics of using the Tk module.

CreateButton creates a GUI button and feeds it a reference to an anonymous subroutine reference ($callback_proc), which holds on to $title, a my variable in its enclosing environment. When the user clicks on the button, the callback is invoked, whereupon it uses its stored value of $title.

Example 4.2: Passing Closures Instead of Ordinary Subroutines

use Tk;
# Creates a top level window
$topwindow = MainWindow->new();
# Create two buttons. The buttons print their names when clicked on. 
CreateButton($topwindow, "hello"); 
CreateButton($topwindow, "world");
Tk::MainLoop();  # Dispatch events.
#--------------------------------------------------------------------
sub CreateButton {
    my ($parent, $title) = @_;
    my($b);
    $callback_proc = sub {
                             print "Button $title pressed\n";
                           };
    $b = $parent->Button(
        '-text'    => "$title",       # Button title
        '-fg'      => 'red',          # foreground color
        '-command' => $callback_proc   # sub to call when the button
                                      # is pressed
    );
    $b->pack();
}

Note that the buttons couldn't care less whether they get references to ordinary subroutines or closures.

4.4.2 Iterators and Streams

An iterator keeps track of where it currently is in a "stream" of entities and returns the next logical entity every time it is called. It is like a database cursor, which returns the next record from a stream of records (the list of records that match the given query). A stream can be bounded (a set of records from a database) or unbounded (a stream of even numbers).

Let's take a look at how closures can be used to represent streams and iterators. The first example is a stream of even numbers and an iterator on this stream that returns the next even number whenever it is called. Clearly, we cannot generate all possible even numbers (as in the bounded case), but we can always compute the next even number if we remember the previous number generated. The iterator remembers this crucial piece of information.

Subroutine even_number_printer_gen takes an integer and returns a subroutine that prints even numbers starting from the given integer.[1] This program is shown in Example 4.3.

[1] This example and explanation are based on Robert Wilensky's excellent book LISPcraft (W.W. Norton and Co.).

Example 4.3: An Even Number Stream Generator

sub even_number_printer_gen {
    # This function returns a reference to an anon. subroutine.
    # This anon. subroutine prints even numbers starting from $input.
    my($input) = @_;   
    if ($input % 2) { $input++};  # Next even number, if the given
                                  # number is odd
    $rs = sub { 
                print "$input ";  # Using $input,which is a my variable 
                                  # declared in an outside scope
                $input  += 2;                 
             };        
    return $rs;   # Return a reference to the subroutine above
}

And now for its usage:

# We want even numbers starting from 30. Ask even_number_printer_gen 
# for a customized iterator that can do such a thing.

$iterator = even_number_printer_gen(30);
# $iterator now points to a closure.
# Every time you call it, it prints the next successive even number.
for ($i = 0; $i < 10; $i++) {
    &$iterator();  
}
print "\n";

This prints

30 32 34 36 38 40 42 44 46 48

$iterator holds on to $input and uses it as private storage subsequently, storing the last even number. Of course, you can create as many iterators as you want, each primed with its own starting number:

$iterator1 = even_number_print_gen (102);
$iterator2 = even_number_print_gen (22);

&$iterator1(); # Prints 102
&$iterator2(); # Prints 22
&$iterator1(); # Prints 104
&$iterator2(); # Prints 24

Notice how each subroutine reference is using its own private value for $input.

Can two closures share the same variables? Sure, as long as they are created in the same environment. Example 4.4 produces two anonymous functions, one that prints even numbers and another that prints odd numbers. Each of these functions prints out the even (or odd) number after the number last printed (by either function), regardless of how many times either of them is called in succession.

Example 4.4: Closures Sharing Variables

sub even_odd_print_gen {
    # $last is shared between the two procedures
    my ($rs1, $rs2);
    my ($last) = shift;  # Shared by the two closures below
    $rs1 = sub { # Even number printer
        if ($last % 2) {$last ++;}
        else { $last += 2};
        print "$last \n";
    };
    $rs2 = sub { # Odd number printer
        if ($last % 2) {$last += 2 }
        else { $last++};
        print "$last \n";
    };
    return ($rs1, $rs2);   # Returning two anon sub references
}

($even_iter,$odd_iter) = even_odd_print_gen(10);
&$even_iter ();  # prints 12
&$odd_iter ();   # prints 13
&$odd_iter  ();  # prints 15
&$even_iter ();  # prints 16
&$odd_iter  ();  # prints 17

This example takes advantage of the fact that Perl can return multiple values from one subroutine, so there is no problem returning references to two anonymous subroutines, both of which happen to be referring to $last. You can call even_odd_print_gen as many times as you want with different seeds, and it keeps returning pairs of subroutine closures. The important point is that to share the same data, the anonymous subroutines must have been created in the same scope. This example also highlights the fact that a closure truly hangs onto the my variables it needs instead of copying or interpolating the variable's values.

4.4.2.1 Random number generation

Let's turn our attention to a more useful example of an unbounded stream, that of a stream of random numbers. The strategy is identical to that used in the previous example: the iterator keeps track of the last generated pseudo-random number.

You might argue that the rand() function represents an iterator primed with a seed (using srand). You are right. But let's say you want to write a simulation program that depends on two independent sources of random number generation. Using rand in both these sources does not make them independent; the reason is that rand happens to calculate a new random number based on the last number it generated (it stores it in a global variable), and calling rand for one stream affects the next number retrieved by the other stream.

Closures provide a nice solution, because they are a combination of code and private data. Instead of using srand, we'll use the function my_srand, which returns a random-number-generating subroutine, seeded with an appropriate initial value. In other words, my_srand is a "generator of random number generators" that returns a custom anonymous subroutine, primed with an initial value for $rand.

In the implementation in Example 4.5, please don't pay too much attention to the algorithm itself (the linear congruential method), because the randomness due to the particular constants chosen has not been tested (it also repeats every 1,000 numbers). Besides, there are much better algorithms.

Example 4.5: A Random-Number-Generating Stream

sub my_srand {
    my ($seed) = @_; 
    # Returns a random number generator function
    # Being predictive, the algorithm requires you to supply a 
    # random initial value.

    my $rand = $seed; 
        return sub  {
             # Compute a new pseudo-random number based on its old value
             # This number is constrained between 0 and 1000.
             $rand = ($rand*21+1)%1000; 
    };
}

We can now use my_srand as many times as we want and get back completely independent closures, each capable of generating random numbers from its own starting point:

$random_iter1 = my_srand  (100);  
$random_iter2 = my_srand (1099);
for ($i = 0; $i < 100; $i++) {
    print $random_iter1(), " ", $random_iter2(), "\n";
}

4.4.3 Closures Versus Objects

If you don't have a background in object orientation, you might be able to understand this section better after you have read Section 7.2, "Objects in Perl".

An object, to give the street definition, is a package of data and functions. The data provides the context for the object's functions to work properly. When you say, for example, $button->setForeground("yellow"), the setForeground function automatically knows which button you are talking about.

In a sense, the facility for closures attempts the same feature - it is also a binding between a subroutine and some private data that is available only to that subroutine. As we saw earlier, in the even_odd_print_gen example, there can be any number of subroutines that can refer to the same basic data, as long as they were all created in exactly the same scope. Abelson, Sussman, and Sussman's delightful Structure and Interpretation of Computer Programs [3] illustrates how to create and use such objects in Scheme (a LISP dialect).

Perl supports a number of features for object orientation (such as inheritance and virtual functions а la C++) that make it easier to create iterators and streams in an object-oriented style than by using closures (the object's attributes reflect the "state" of the iterator). Closures are also much more space-intensive than objects but a trifle faster; we will study the reason for this in Chapter 20.

I prefer objects to closures in all cases except one: callback procedures. I find it easier to implement callbacks with simple closures than to create "callback objects," as you might typically do in C++ (and have to, in Java). In the CreateButton example above, you could create a callback object with exactly one "method," say, execute(). The button would call the method $callback_object->execute() when it was clicked upon, and the execute method of that object would know exactly what to do. The callback object can store all the context for execute to work. Instead of all this work, it is simpler and more direct to use closures, because they automatically squirrel away the required context.

Tom Christiansen's perltoot document (toot stands for Tom's Object-Oriented Tutorial [2]) implements objects using closures to represent the objects' state. It is an interesting approach, but I don't use it because there are simpler approaches for obtaining privacy; besides, they are faster too. More on this in Chapter 7, Object-Oriented Programming.


Previous: 4.3 ClosuresAdvanced Perl ProgrammingNext: 4.5 Comparisons to Other Languages
4.3 ClosuresBook Index4.5 Comparisons to Other Languages