rdfield has asked for the wisdom of the Perl Monks concerning the following question:

This is my first foray into closures and callbacks and I just wanted some reassurance that the code is working as well as it seems to be, i.e. it produces the expected results but I just want to make sure that I'm doing it the "right way":
#!/usr/perl/bin/perl -w use strict; my @indexes = qw (3 0 4 5 6 7); open INDATA,"<my.dat" or die "my.dat: $!"; sub read_row { my $row = <INDATA>; return unless defined($row); return (split(/\s+/,$row))[@indexes]; } test::process(\&read_row); close INDATA; package test; sub process { my $callback = shift; while (my @row = &$callback) { print "test : " . join(",",@row) . "\n"; } } 1;
and the data:
c1 ct1 p2 d1 12 15 21 91 c1 ct2 p2 d3 15 25 23 93 c1 ct3 p1 d1 13 15 21 93 c1 ct1 p1 d2 12 15 21 92 c1 ct2 p3 d1 11 25 24 92 c2 ct3 p3 d3 12 15 21 96 c2 ct1 p3 d1 16 35 22 92 c2 ct2 p1 d2 12 15 21 99 c2 ct3 p1 d1 12 05 23 91 c2 ct1 p2 d2 99 15 21 93 c2 ct2 p1 d1 12 25 24 99
and the results:
test : d1,c1,12,15,21,91 test : d3,c1,15,25,23,93 test : d1,c1,13,15,21,93 test : d2,c1,12,15,21,92 test : d1,c1,11,25,24,92 test : d3,c2,12,15,21,96 test : d1,c2,16,35,22,92 test : d2,c2,12,15,21,99 test : d1,c2,12,05,23,91 test : d2,c2,99,15,21,93 test : d1,c2,12,25,24,99
(I'm using 5.00503 hence the bareword filehandles).

Any comments welcome and appreciated.

rdfield

Replies are listed 'Best First'.
Re: Closures and callbacks...
by nothingmuch (Priest) on Oct 23, 2002 at 11:33 UTC
    I'd like to point out that these aren't really closures... You're doing this one hundred percent, and there's nothing wrong. Closures, however, contain copies of lexical values that they were created beside, and those copies are kept independantly for each instance of the closure. For example:
    sub add { my $var = shift; sub { $var + $_[0] }; } my $bar = add(3); my $foo = add(5); $\ = "\n"; print &$bar(6); print &$foo(6);
    This can be useful for initiallizing once, something that must be done repeatedly - in principal similar to objects.

    Update: See more accurate closure definition in broquaint's reply.

    Update: I apologize for all the inacuracies, please consider my node deprecated... ;-)
    I may have been having too much sugar and not enough sleep at the time...

    -nuffin
    zz zZ Z Z #!perl
      I'd like to point out that these aren't really closures...
      The read_row function is a closure, it just happens to be a non-encapsulated closure as process can also see @indexes. It would act as a more effective closure if it was re-structured like so
      use Symbol; { my @indexes = qw (3 0 4 5 6 7); my $fh = gensym; open $fh,"<my.dat" or die "my.dat: $!"; sub read_row { my $row = <$fh>; close $fh and return unless defined($row); return (split(/\s+/,$row))[@indexes]; } }

      HTH

      _________
      broquaint

        I'm still very new to closures, but I just don't get it. Why is read_row a closure? Doesn't read_row just return a list of values, instead of code?

        -- Dan

      I'd like to point out that all the forms of read_row presented above are in fact closures. It is closed over the lexical variable @indexes.

      nothingmuch's explanation of closures is not correct. A closure does not contain a "copy" of the lexical variable, nor is the variable "kept independently for each instance". Quite the opposite: a closure maintains a reference to a lexical variable outside its scope, and all instances of the closure(s) all refer to the exact same variable that was in existence at the time the closure was created.

      For variables and functions which are instantiated at compile time, as in the original post, the situation is very simple. Here's two functions which are both closed over the same variable:

      my @indexes = qw (3 0 4 5 6 7); open INDATA,"< my.dat" or die "my.dat: $!"; sub read_row { my $row = <INDATA>; return unless defined($row); return (split(/\s+/,$row))[@indexes]; } sub get_indexes { return @indexes; }
      Closures get more useful, but more tricky, when the functions in question are generated at runtime. Here's how I would make the original poster's code more useful by creating the closure on the fly:
      use IO::File; sub process_each_file_line { my( $filename, $preprocess_cb, $process_cb ) = @_; my $fh = IO::File->new( "< $filename" ) or die "open $filename for reading: $!"; while ( <$fh> ) { $process_cb->( $preprocess_cb->() ); } } sub process { my( $filename, @indexes ) = @_; my @result; process_each_file_line( $filename, sub { (split)[ @indexes ] }, sub { local $" = ','; push @result, "test : @_\n"; } ); return @result; } # call the function: print process( "my.dat", qw( 3 0 4 5 6 7 ) );
      Within the process function, the first anonymous sub (sometimes referred to as a "lambda" -- don't ask) is closed over the @indexes variable, and the second one is closed over @result.

      Since @indexes and @result are scoped to the process function, new instances of them are created each time process is called. And the two closures that use them have the same lifetimes. What happens if the closures live longer than the variables they're closed over? Perl does the right thing: it keeps the lexical variables alive. It's all just reference-counting, after all. A (rather contrived) example follows.

      use IO::File; sub process_each_file_line { my( $filename, $preprocess_cb, $process_cb ) = @_; my $fh = IO::File->new( "< $filename" ) or die "open $filename for reading: $!"; while ( <$fh> ) { $process_cb->( $preprocess_cb->() ); } } sub make_file_processor { my( $filename, @indexes ) = @_; return sub { # make a closure which takes no args. my @result; process_each_file_line( $filename, sub { (split)[ @indexes ] }, sub { local $" = ','; push @result, "test : @_\n"; } ); return @result; } } # create the processor for the given input params: my $processor = make_file_processor( "my.dat", qw( 3 0 4 5 6 7 ) ); # later on, call the closure: print $processor->();
      In this example, we actually have two levels of closures. The third argument to process_each_file_line (the second lambda) is closed over @result, which exists one lexical level above. But the second argument (the first lambda) is closed over @indexes, which exists two lexical levels above. Not that it's any big deal; perl lets us do this, and takes care of all the nasty reference bookkeeping.

      Disclaimer: The above code was not tested before posting, and may contain bugs.