Beefy Boxes and Bandwidth Generously Provided by pair Networks Joe
Welcome to the Monastery
 
PerlMonks  

Style Question on Closures

by jynx (Priest)
on Jan 01, 2002 at 06:19 UTC ( #135473=perlquestion: print w/ replies, xml ) Need Help??
jynx has asked for the wisdom of the Perl Monks concerning the following question:


Recently while developing a script i thought i found a good use for closures. The script itself is a pretty simple log parsing script, and on the whole i didn't need to use a closure, but the chance to learn got hold of me and i decided to use one anyway :-) Simply put, i'm running through a loop and i want to initialize an array with certain settings every time through. The settings are actually user-dependant and read from a file. Since reading from a file many times is a slow-up on the rest of the code i only wanted to read it once.

Of course i wouldn't be posting unless there was a problem. It turns out that i can use simple scalars/arrays/hashes just fine, but a code ref doesn't get "parsed"1 correctly. The problem i ran into is that the closure must be "parsed" before any code that calls it can run or else strict will die with the message:

Can't use string ("") as a subroutine ref while "strict refs" in use a +t temp line 21.
<READ MORE> My solution was a BEGIN block, but i'm wondering if that's the best solution. Anything that requires magic to work makes me wonder about my design. So with that, here's a code example with documentation (using perl5.6.0). Please tell me if there's a better solution.

#!/usr/bin/perl -w use strict; # To test this uncomment these lines and (un)comment BEGIN {} #my @strings = get_init_strings(); #print "strings: @strings\n"; BEGIN { { # begin closure my @strings; my $get_strings = sub { # Trivialized code here for brevity. # This actually reads in from a file with lots of error checking. return "this", "that", "the", "other"; }; # the actual subroutine, the only thing callable outside of the clos +ure sub get_init_strings { if (!@strings) { # only call $get_strings o +nce @strings = $get_strings->(); } return @strings; } } # close closure } # close BEGIN block
Since i want to have the actual main code at the top of the file (for maintainability) and not the subroutines, this is what i resorted to. The only other way i could see to get it to "parse" this first is to have a subroutine return this code as a string, and then eval the string at the top of the file. That sounded too icky so i avoided it. Either way i have to document this well so later maintainers don't ponder for 3 days why i did this.

Also, i searched for related topics and came up with this but testing showed that mentioning $get_strings again didn't solve the problem, so i don't think it's related. And if there's something in merlyn's WebTechnique column about this i didn't catch it, and those were the only highly pertinent links i found on the matter (but i have been known to be blind :-)

Any help with style, suggestions, otherwise is greatly appreciated,
jynx

1 "Parsed" is in quotes here because that's almost but not quite what i mean when i say that. IIRC there's a lot more that goes on than just "parsing"...

Comment on Style Question on Closures
Select or Download Code
Re: Style Question on Closures
by chip (Curate) on Jan 01, 2002 at 06:49 UTC
    Your use of BEGIN is entirely appropriate for the code you posted. A normal sub definition is processed at compile time. What you're doing with the assignment to $get_strings is basically a sub definition, but since it takes the form of an assignment, Perl doesn't know that it should be handled at compile time. The BEGIN block forces the assignment to happen when it's needed. That's what BEGIN is for.

    That said....

    I don't see any real use being made of the neat features of a closure here. You could replace $get_strings with a normal subroutine &get_strings and it would work the same.

    Perhaps you should choose a different sandbox to build this particular kind of castle....

        -- Chip Salzenberg, Free-Floating Agent of Chaos


      Hmm,

      Firstly, thanks for the reply, i probably should've read up on subroutine parsing before posting, but i was blind to that apparently as it didn't even occur to me (*sigh* a post wasted on an RTFM question, sorry about that). That said, while it's true i didn't need to use a closure, this was practice. The reason for making the sub anonymous was so that only the get_init_strings subroutine could call it, which is a good intent, although admittedly completely wasted here. If i weren't so intent on using something i've never used before it would probably be closer to this (this code needs you to see where the loop happens since it's basically an orcish manuever):

      # Code simplified for brevity (again) my @files = get_files(); my $strings_file = shift(@ARGV) || "default"; my @init_strings; foreach my $file (@files) { my @strings = @init_strings || get_init_strings($strings_file, \@init_strings); push @strings, extra_strings(); # manipulate files here ... } sub get_init_strings { my $file = shift; my $strings = shift; open FILE, $file or die "Couldn't open $file: $!\n"; push @$strings, $_ while <FILE>; close FILE, $file or warn "Couldn't close $file: $!\n"; return @$strings; }
      This isn't tested, just the first thing that came to mind. And it would do what i did without any of the hassle. But it's not as much of a learning experience nor as fun :-)

      jynx

        There are practical uses for call-backs. One use is to provide some user of a Perl module a way to modify the behavior of that module. Here is a very simple example:
        use strict; package Foo; use Carp; sub new { my ($class,$msg) = @_; my $self = {msg => $msg}; return bless $self,$class; } sub print_me { my ($self,$cb) = @_; croak "not a code ref" unless ref $cb eq 'CODE'; return $cb->($self->{'msg'}); } package main; my $foo = Foo->new('hello world'); print $foo->print_me(sub {ucfirst shift}), "\n";
        Now the user can specify how they want the message printed. Like i said, a very simple example, but i am sure you can imagine this module doing a lot more behind the scenes. In these cases, providing a call-back instead of providing a list of choices can make a module more flexible, and keep you from maintaining that list of choices.

        UPDATE - drats. Just when i though i knew what a closure was .... perrin pointed out that this is indeed, not a closure ... a good example for a anonymous subs maybe, but not a closure. I stand corrected and apologetic to the 13 people that voted this node up.

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        F--F--F--F--F--F--F--F--
        (the triplet paradiddle)
        
Re: Style Question on Closures
by Juerd (Abbot) on Jan 02, 2002 at 00:06 UTC
    A closure is a code block with lexcially scoped variables. But only use them if you've got a good reason for that.

    This is a simple example of what a closure is and how it works:

    sub closure { my $a = $_[0]; return sub { $a += 2 }; } my $first = closure(1); print $first->(), "\n"; # 3 print $first->(), "\n"; # 5 my $second = closure(14); print $second->(), "\n"; # 16 print $first->(), "\n"; # 7
    Only with dynamic information (like the $a's in this example), closures are useful.

    2;0 juerd@ouranos:~$ perl -e'undef christmas' Segmentation fault 2;139 juerd@ouranos:~$

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://135473]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (14)
As of 2014-04-16 10:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (423 votes), past polls