Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Closure on Closures

by broquaint (Abbot)
on Jun 25, 2003 at 16:08 UTC ( [id://268891]=perlmeditation: print w/replies, xml ) Need Help??

Closure on Closures

Before we get into this tutorial we need to define what a closure is. The Camel (3rd edition) states that a closure is

when you define an anonymous function in a particular lexical scope at any particular moment
However, I believe this isn't entirely accurate as a closure in perl can be any subroutine referring to lexical variables in the surrounding lexical scopes.[0]

Now with that (simple?) definition out of the way, we can get on with the show!

Before we get started ...

For one to truely understand closures a solid understanding of the principles of lexical scoping is needed, as closures are implemented through the means of lexical scoping interacting with subroutines. For an introduction to lexical scoping in perl see Lexical scoping like a fox, and once you're done with that, head on back.

Right, are we all here now? Bueller ... Bueller .. Bueller? Good.
Now that we have our basic elements, let's weave them together with a stitch of explanation and a thread of code.

Hanging around

Now as we all know, lexical variables are only active for the length of the surrounding lexical scope, but can be kept around in an indirect manner if something else references them e.g

1: sub DESTROY { print "stick a fork in '$_[0]' it's done\n" } 2: 3: my $foo = bless []; 4: { 5: my $bar = bless {}; 6: ## keep $bar around 7: push @$foo => \$bar; 8: 9: print "in \$bar's [$bar] lexical scope\n"; 10: } 11: 12: print "we've left \$bar's lexical scope\n"; __output__ in $bar's [main=HASH(0x80fbbf0)] lexical scope we've left $bar's lexical scope stick a fork in 'main=ARRAY(0x80fbb0c)' it's done stick a fork in 'main=HASH(0x80fbbf0)' it's done
The above example illustrates that $bar isn't cleaned up until $foo, which references it, leaves the surrounding lexical scope (the file-level scope in this case). So from that we can see lexical variables only stick around for the length of the surrounding scope or until they're no longer referenced.

But what if we were to re-enter a scope where a variable is still visible, but the scope has already exited - will the variable still exist?

1: { 2: my $foo = "a string"; 3: INNER: { 4: print "\$foo: [$foo]\n"; 5: } 6: } 7: goto INNER unless $i++; __output__ $foo: [a string] $foo: []
As we can see the answer is categorically 'No'. In retrospect this is quite obvious as $foo has gone out of scope and there is no longer a reference to it.

A bit of closure

However, the last example just used a simple bareblock, now let's try it with a subroutine as the inner block

1: { 2: my $foo = "a string"; 3: sub inner { 4: print "\$foo: [$foo]\n"; 5: } 6: } 7: inner(); 8: inner(); __output__ $foo: [a string] $foo: [a string]
"Hold on there cowboy - $foo has already gone out of scope at the time of the first call to inner() let alone the second, what's going on there?!?", or so one might say. Now hold your horses, there is a very good reason for this behaviour - the subroutine in the example is a closure. "Ok, so it's a closure, but why?", would be a good question at this point. The reason is that subroutines in perl have what's called a scratchpad which holds references to any lexical variables referred to within the subroutine. This means that you can directly access lexical variables within subroutines even though the given variables' scope has exited.

Hmmm, that was quite a lot of raw info, so let's break it down somewhat. Firstly subroutines can hold onto variables from higher lexical scopes. Here's a neat little counter example (not counter-example ;)

1: { 2: my $cnt = 5; 3: sub counter { 4: return $cnt--; 5: } 6: } 7: 8: while(my $i = counter()) { 9: print "$i\n"; 10: } 11: print "BOOM!\n"; __output__ 5 4 3 2 1 BOOM!
While not immediately useful, the above example does demonstrate a subroutine counter() (line 3) holding onto a variable $cnt (line 2) after it has gone out of scope. Because of this behaviour of capturing lexical state the counter() subroutine acts as a closure.

Now if we look at the above example a little closer we might notice that it looks like the beginnings of a basic iterator. If we just tweak counter() and have it return an anonymous sub we'll have ourselves a very simple iterator

1: sub counter { 2: my $cnt = shift; 3: return sub { $cnt-- }; 4: } 5: 6: my $cd = counter(5); 7: while(my $i = $cd->()) { 8: print "$i\n"; 9: } 10: 11: print "BOOM!\n"; __output__ 5 4 3 2 1 BOOM!
Now instead of counter() being the closure we return an anonymous subroutine (line 3) which becomes a closure as it holds onto $cnt (line 2). Every time the newly created closure is executed the $cnt passed into counter() is returned and decremented (this post-return modification behaviour is due to the nature of the post-decrement operator, not the closure).

So if we further apply the concepts of closures we can write ourselves a very basic directory iterator

1: use IO::Dir; 2: 3: sub dir_iter { 4: my $dir = IO::Dir->new(shift) or die("ack: $!"); 5: 6: return sub { 7: my $fl = $dir->read(); 8: $dir->rewind() unless defined $fl; 9: return $fl; 10: }; 11: } 12: 13: my $di = dir_iter( "." ); 14: while(defined(my $f = $di->())) { 15: print "$f\n"; 16: } __output__ . .. .closuretut.html.swp closuretut.html
In the code above dir_iter() (line 3) is returning an anonymous subroutine (line 6) which is holding $dir (line 4) from a higher scope and therefore acts as a closure. So we've created a very basic directory iterator using a simple closure and a little bit of help from IO::Dir.

Wrapping it up

This method of creating closures using anonymous subroutines can be very powerful[1]. With the help of Richard Clamp's marvellous File::Find::Rule we can build ourselves a handy little grep like tool for XML files

1: use strict; 2: use warnings; 3: 4: use XML::Simple; 5: use Getopt::Std; 6: use File::Basename; 7: use File::Find::Rule; 8: use Data::Dumper; 9: 10: $::PROGRAM = basename $0; 11: 12: getopts('n:t:hr', my $opts = {}); 13: 14: usage() if $opts->{h} or @ARGV == 0; 15: 16: my @dirs = $opts->{r} ? @ARGV : map dirname($_), @ARGV; 17: my @files = $opts->{r} ? '*.xml' : map basename($_), @ARGV; 18: my $callback = gensub($opts); 19: 20: my @found = find( 21: file => 22: name => \@files, 23: ## handy callback which wraps around the callback created above 24: exec => sub { $callback->( XMLin $_[-1] ) }, 25: in => [ @dirs ] 26: ); 27: 28: print "$::PROGRAM: no files matched the search criteria\n" and exi +t(0) 29: if @found == 0; 30: 31: print "$::PROGRAM: the following files matched the search criteria +\n", 32: map "\t$_\n", @found; 33: 34: exit(0); 35: 36: sub usage { 37: print "Usage: $::PROGRAM -t TEXT [-n NODE -h -r] FILES\n"; 38: exit(0); 39: } 40: 41: sub gensub { 42: my $opts = shift; 43: 44: ## basic matcher wraps around the program options 45: return sub { Dumper($_[0]) =~ /\Q$opts->{t}/sm } 46: unless exists $opts->{n}; 47: 48: ## node based matcher wraps around options and itself! 49: my $self; $self = sub { 50: my($tree, $seennode) = @_; 51: 52: for(keys %$tree) { 53: $seennode = 1 if $_ eq $opts->{n}; 54: 55: if( ref $tree->{$_} eq 'HASH') { 56: return $self->($tree->{$_}, $seennode); 57: } elsif( ref $tree->{$_} eq 'ARRAY') { 58: return !!grep $self->($_, $seennode), @{ $tree->{$_} }; 59: } else { 60: next unless $seennode; 61: return !!1 62: if $tree->{$_} =~ /\Q$opts->{t}/; 63: } 64: } 65: return; 66: }; 67: 68: return $self; 69: }
Disclaimer: the above isn't thoroughly tested and isn't nearly perfect so think twice before using in the real world

The code above contains 3 simple examples of closures using anonymous subroutines (in this case acting as callbacks). The first closure can be found on in the exec parameter (line 24) of the find call. This is wrapping around the $callback variable generated by the gensub() function. Then within the gensub() (line 41) there are 2 closures which wrap around the $opts lexical, the second of which also wraps around $self which is a reference to the callback which is returned.

Altogether now

So let's bring it altogether now - a closure is a subroutine which wraps around lexical variables that it references from the surrounding lexical scope which subsequently means that the lexical variables that are referenced are not garbage collected when their immediate scope is exited.

There ya go, closure on closures! Hopefully this tutorial has conveyed the meaning and purpose of closures in perl and hasn't been too confounding along the way.

Thanks to virtualsue, castaway, Corion, xmath, demerphq, Petruchio, tye for help during the construction of this tutorial

[0] see. chip's Re: Toggling between two values for a more technical definition (and discussion) of closures within perl
[1] see. tilly's Re (tilly) 9: Why are closures cool?, on the pitfalls of nested package level subroutines vs. anonymous subroutines when dealing with closures


Disclaimer: Not everyone will agree with the terminology (I imagine) so as long as you don't find the descriptions wildly off the mark or generally misleading then they're likely to stay as they are.

Update - revised the second and third sections by dropping any references to 'reference counting'

Replies are listed 'Best First'.
Re: Closure on Closures (beta)
by adrianh (Chancellor) on Jun 25, 2003 at 16:36 UTC

    ++ Nice summary.

    Minor niggle. If it were me I would put a little bit more emphasis on lexical scoping and a little less emphasis on reference counting. When the value of a lexical variable is garbage collected is independent of whether a Perl closure is created. It's about scope - not memory usage. For example:

    our $Global; sub make_closure { my %lexical; $Global = \%lexical; return sub { my $key = shift; @_ ? $lexical{$key} = shift : $lexical{$key}; }; }; { # here we make a closure my $c = make_closure(); # which we can use to set and get keys $c->(foo => 42); print "foo is ", $c->('foo'), "\n"; # at the end of the scope the closure goes away }; # but the referant is still around print "Global foo is ", $Global->{foo}, "\n";

    Update: It might also be worth comparing what Perl does to languages without lexical capture like C.

      the examples in this section are some what complicated for a new learner of perl.specially calling subroutines

      Title edit by tye, restore prefix

Re: (On Speed Diff, etc.) Closure on Closures (beta)
by chunlou (Curate) on Jun 25, 2003 at 23:12 UTC
    I read somewhere that said something like:
         Object is data wrapped in methods.
         Closure is subroutine wrapped in data.
    Instead of talking about the philosophical differences first, I ran some benchmarks to compare closure and object, just for the fun of it. I have the following three results:

    use strict; use warnings; use Benchmark qw(cmpthese); # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub closure_counter { my $count = shift; return sub { $count++ }; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {package obj_counter; sub new {bless {count=>$_[1]};} sub count {return ($_[0]->{count})++} } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $obj_counter = obj_counter->new(3); my $closure_counter = closure_counter(3); cmpthese(1000, { obj_counter=>sub{$obj_counter->count()for 1..1000;}, closure_counter=>sub{$closure_counter->()for 1..1000;} } ); __END__ Benchmark: timing 1000 iterations of closure_counter, obj_counter... closure_counter: 2 wallclock secs ( 2.58 usr + 0.00 sys = 2.58 CPU) + @ 387.60/s (n=1000) obj_counter: 5 wallclock secs ( 5.22 usr + 0.00 sys = 5.22 CPU) @ 1 +91.57/s (n=1000) Rate obj_counter closure_counter obj_counter 192/s -- -51% closure_counter 388/s 102% --

    Directory Iterator:
    use strict; use warnings; use Benchmark qw(cmpthese); # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - use IO::Dir; sub closure_dir_iter { my $dir = IO::Dir->new(shift); return sub { my $fl = $dir->read(); $dir->rewind() unless defined $fl; return $fl; }; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {package obj_dir_iter; use IO::Dir; sub new {bless {dir=>IO::Dir->new($_[1])};} sub iter { my $fl = $_[0]->{dir}->read(); $_[0]->{dir}->rewind() unless defined $fl; return $fl; } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $obj_dir_iter = obj_dir_iter->new( "." ); my $closure_dir_iter = closure_dir_iter( "." ); cmpthese(500, { obj_dir_iter=>sub{while(defined(my $f = $obj_dir_iter->iter()) +){print "$f\n";}}, closure_dir_iter=>sub{while(defined(my $f = $closure_dir_iter- +>())){print "$f\n";}} } ); __END__ Benchmark: timing 2000 iterations of closure_dir_iter, obj_dir_iter... obj_dir_iter: 1 wallclock secs ( 1.20 usr + 0.00 sys = 1.20 CPU) @ +1666.67/s (n=2000) closure_dir_iter: 1 wallclock secs ( 1.10 usr + 0.00 sys = 1.10 CPU +) @ 1818.18/s (n=2000) Rate obj_dir_iter closure_dir_iter obj_dir_iter 1667/s -- -8% closure_dir_iter 1818/s 9% --

    Turtle Graph (drawing Koch curve):
    use strict; use warnings; use Benchmark qw(cmpthese); # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - use constant PI => 3.14159265359; sub closure_turtle { my ($h, $xy) = (0, [[0],[0]]); # h = heading (0 - north, 90 - +east, etc) return sub { $h = $h + (shift || 0); # accumulative turns in degree my $d = shift || 0; # distance $xy->[0][scalar(@{$xy->[0]})] = $d*sin(PI*$h/180) + $xy->[0][$ +#{@{$xy->[0]}}]; $xy->[1][scalar(@{$xy->[1]})] = $d*cos(PI*$h/180) + $xy->[1][$ +#{@{$xy->[1]}}]; return $xy; }; } sub closure_koch { my ($turtle, $d, $level) = @_ ; if ($level==0) {$turtle->(0,$d); return 1;} $turtle->( 0,0); closure_koch($turtle,$d/3,$level-1); $turtle->(-60,0); closure_koch($turtle,$d/3,$level-1); $turtle->(120,0); closure_koch($turtle,$d/3,$level-1); $turtle->(-60,0); closure_koch($turtle,$d/3,$level-1); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {package obj_turtle; use constant PI => 3.14159265359; sub new {return bless({h=>0,xy=>[[0],[0]]});} sub rt {$_[0]->{h}=$_[0]->{h}+$_[1];} # right turn by x + degrees sub fd { # forward by x poi +nts my ($h, $xy, $d) = ($_[0]->{h}, $_[0]->{xy}, $_[1]); $xy->[0][scalar(@{$xy->[0]})] = $d*sin(PI*$h/180) + $xy->[0][$ +#{@{$xy->[0]}}]; $xy->[1][scalar(@{$xy->[1]})] = $d*cos(PI*$h/180) + $xy->[1][$ +#{@{$xy->[1]}}]; $_[0]->{xy} = $xy; return $xy; } } sub obj_koch { my ($turtle, $d, $level) = @_ ; if ($level==0) {$turtle->fd($d); return 1;} $turtle->rt( 0); obj_koch($turtle, $d/3,$level-1); $turtle->rt(-60); obj_koch($turtle, $d/3,$level-1); $turtle->rt(120); obj_koch($turtle, $d/3,$level-1); $turtle->rt(-60); obj_koch($turtle, $d/3,$level-1); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $obj_turtle = obj_turtle->new(); my $closure_turtle = closure_turtle(); cmpthese(100, { obj_turtle=>sub{for(0..2){$obj_turtle->rt(120); obj_koch($obj_ +turtle,170,4);}}, closure_turtle=>sub{for(0..2){$closure_turtle->(120, 0); closu +re_koch($closure_turtle,170,4);}} } ); __END__ Benchmark: timing 100 iterations of closure_turtle, obj_turtle... closure_turtle: 6 wallclock secs ( 6.49 usr + 0.00 sys = 6.49 CPU) +@ 15.41/s (n=100) obj_turtle: 5 wallclock secs ( 4.99 usr + 0.00 sys = 4.99 CPU) @ 20 +.04/s (n=100) Rate closure_turtle obj_turtle closure_turtle 15.4/s -- -23% obj_turtle 20.0/s 30% --

    Closure counter beat object's by ~100%; Closure directory iterator beat object's by ~10%; but object turtle beat closure's by ~30%.

    Why the difference?

    Going from empirical to completely academic and philosophical, closure has pretty good application in lambda calculus. Consider the following code (we use "%" place of "lambda"):
    # IF = %b.(%x.(%y.(b x) y)) $IF = sub { my $b = shift; sub { my $x = shift; sub { my $y = shift; $b->($x)->($y); } } } # TRUE = %x.(%y.x) $TRUE = sub { my $x = shift; sub { my $y = shift; $x; } } # FALSE = %x.(%y.y) $FALSE = sub { my $x = shift; sub { my $y = shift; $y; } } print $IF->($TRUE)->("then")->("else"); # prints "then" print $IF->($FALSE)->("then")->("else"); # prints "else"
    To verify algebraically
            (IF TRUE A B)
    observe that
            (%b.%x.%y.(b x y) %a.%b.a A B)
               (%x.%y.(%a.%b.a x y)   A B)
                  (%y.(%a.%b.a A y)     B)
                      (%a.%b.a A B)
                         (%b.A   B)
    Likewise, (IF FALSE A B) gives us B. (Full discussion can be found here.)

    Is it a big deal? Well, set theory and mathematical logic may look naively useless (which after all only give us Incompleteness Theorem); or number theory or group/fields theory may seem trivial (where only comes some obscure Elliptic Curves for us to make highly unbreakable encryption).

    Lisp, considered a functional language, where closure's considered a major technique, is often used to implement A.I. stuff--somewhere along the line, there come RegEx, Mathematica, and taken-for-granted stuff like that.

    Is closure (and functional programming) for everyone? Not when a third of the bookshelves in Computer section in the major bookstores are stuffed with Java and OOP. It's hard to be Func-ie when almost everyone else is Ob-ing.

    But then, if somebody could find good use of group theory, somebody could surely find meaningful application of closure and functional programming. It's just a matter of creativity and practice.
      Object is data wrapped in methods.
      Closure is subroutine wrapped in data

      It's actually the other way around. An object exposes data, a closure exposes code. As Tom C. used to say:

      An object is data that knows which code acts on it.
      A closure is code that knows which data to act on.


        And you should know, Abigail!!!

        Actually, as Abigail just did (go quickly and grab Closures-1.2 in, you can construct a whole object system on top the closures (it's been done some dozen times in different ways in Scheme, where basically everything is a closure).

        A simpler example, consider this code:

        use strict; use warnings; sub Person { my %self = (); my @data = ( qw/name age/ ); my %mem = (); sub { for my $sub ( @data ) { $self{$sub} = sub { $mem{$sub} = $_[0] if @_; return $mem{$sub} } } return \&{$self{$_[0]}} if defined $self{$_[0]}; die "Method '$_[0]' unknown to class Person"; } }
        We create a closure which exposes the interface of the class and holds both the methods (in %self) and data (in %mem, from memory). Please, note that it's impossible to read or write to the values held in %mem or %self (ie, modify the state of the object or modify its interface) directly, only through the interface exposed, which is very hygienic in itself. Using this is easy (if somewhat not very idiomatic):
        # creating object my $john = Person; # initializing values $john->('name')->('John'); $john->('age')->(23); # working with object print "I'm ", $john->('name')->(), " and I am ", $john->('age')->(), " years-old, but my birthday is in just 2 seconds\n"; sleep( 2 ); $john->('age')->(24); print "Happy B-Day, ", $john->('name')->(), "!!!\n"; print "It's ", $john->('age')->(), " years-old already!!!\n"; print $john->('address')->(); __END__

        Output is as expected (text between []'s is mine):

        I'm John and I am 23 years-old, but my birthday is in just 2 seconds [two seconds pass...] Happy B-Day, John!!! It's 24 years-old already!!! Uncaught exception from user code: Method 'address' unknown to class Person at line 18 +. main::__ANON__('address') called at line 40

        I am sure it's not too hard being able to force the dereference of the methods when you are on getter mode so instead of writing $john->('age')->() you can just say $john->('age') (as Abigail has in Closures-1.2). Probably an additional line checking the number of arguments passed to the method.

        Except being the state held in the closures, this is all pure functional code; functional programming rules, yeah :)

        best regards,

        our $Perl6 is Fantastic;

      Your object method calls are slower because the add on ISA lookup/maintenance (just because it is a method), a variable dereference and a hash lookup. The base closure is just a function and postincrement. It makes sense for one to be slower, both perform the same task, one has to do a bit more janitorial work. Consider though, that your closure might be heavier in memory requirements. Does any of this really matter? Probably not. This is not the sort of thing I worry about. Generally my decision on how to organize my data is driven by other requirements than method call overhead. In fact, it always is. Except for once when I was doing a really, really big batch job. Then I used plain functions.

        In short, (in Perl at least) closure is faster than object due to less dereferencing overhead but could be slower due to larger memory requirement? (In the test, the edge of closure over object was diminishing and even reversed when the tasks were getting more "heavy-duty.")

        Different question, if I may. I want to focuse on Design, as opposed to Programming.

        How would you characterize and procedurize a "functional design process" (if there is such a thing). Like a user manual on Design: the Functional (or Closure) Way.

        I hope it's self-evident that design and programming are not always an integrated process. Many people program in Java the imperative or procedural way pretty much, nothing so OO about the design of the codes.

        On the other hand, a design framework could be applied more generally even when the tools are not integrated with it. Like, OO Design is a good framework to guide your thinking process to characterize and generalize some human verbal requirements into classes. For instance, you could think of a Webpage being a class and the stored procedure associated with it being methods, even though HTML, SQL, etc are not OO.

        Besides the characterization of the process, what would be the examples constrasting the closure/functional of design versus the other whatever ways, along with the advantages--design-wise or conceptual--such as being more succinct, flexible, insightful, etc? (Like, in physics, some people do things the group-theoretic way vs. the good old calculcus way; in economics, game-theoretic vs others. They produce different insights into the same problems and sometimes even different conclusions.)

        Take the scripts in the test as examples. Closure counter certainly looks like a cleaner design (or model); object is probably an overkill in this case. With the Turtle, if we need more "methods," object seems like a natural way to go. With directory iterator, I really have no preference towards either.


        Update: As a afterthought, maybe some examples on translating some existing OO design/code into closure one (if applicable) would be pedagogically more instructive? Since I suppose more people can relate to OO than closure.
      I've seen objects actually implemented with a closure (like in this tutorial), as a way to give your objects a 'private/public' distinction. I wonder what would that do to performance...
        Right, that's a very clever use of closure. Maybe I didn't read enough, but I don't see such technique being used too often. It's good for avoiding more deliberate tempering with variables in a package. Isn't the usual lexical scope good enough to avoid accidental clashes between things?
Re: Closure on Closures (beta)
by sauoq (Abbot) on Jul 07, 2003 at 02:52 UTC
    However, I believe this isn't entirely accurate as a closure in perl can be any subroutine referring to lexical variables in the surrounding lexical scopes.

    I might take it a step further and say "any code." Here is an example where someone accidentally created a closure with a code assertion in a regular expression.

    I also offer the following variation on your bare block example for discussion:

    { my $foo = 99; CODE: { print ++$foo, "\n" }; } goto CODE;
    It isn't intuitive, but that will print "100" once and then print integers counting up from 1. Given that the storage isn't the same on subsequent entries into the bare block as it is on the first one, one might argue that it isn't a closure at all. It makes sense, however, to think of it as a closure that is created on the first entry into the bare block via the goto. Of course, that makes it impossible to initialize the data. So, it's not very useful (except maybe for obfuscations.)

    "My two cents aren't worth a dime.";

      I don't think this would fit in the normal CS definition of a closure. With closures you are creating a data structure that holds some code and the environment the code will be evaluated in.

      In your example you're moving to some code that's inside a scope, rather than creating some code that carries it's scope with it.

        I don't think this would fit in the normal CS definition of a closure.

        I think the "normal CS definition of a closure" is a pretty elusive beast; mythical, maybe. I've been unsuccessfully looking for one since tye challenged me to find one in his reply to this node. Most of the time, the term is defined within the context of a particular language. Usually, it is demonstrated rather than defined at all.

        Insofar as creating a definition, I disagree with tye about the importance of being able to "hide different lexicals into seperate refences to the same subroutine." (I absolutely agree that being able to do so is what really makes closures useful, however.)

        If tye had tasked me with writing a generic definition for "closure" rather than finding one, I would have responded with something similar to this:

        A closure is code that retains a binding to a free variable even after the variable has gone out of scope.
        I think that describes what a closure is without resorting to explaining what one is useful for, or how one is implemented.

        In your example you're moving to some code that's inside a scope, rather than creating some code that carries it's scope with it.

        Well, yes and no. (And this is, at least in part, why I submitted it for discussion.) I suggested that the closure is actually created when the sub is first entered via the goto. At that point, the variable has gone out of scope. Then we jump back into the code after the declaration. Really, it's very similar to broquaint's "named closure" example; only, the inner block is not a subroutine. I don't know the details of what perl is doing with the relevant scratchpads at that point, and I'd be happy for someone to explain it to me. I suspect that the variable is re-initialized to undef because perl doesn't ever expect a bare block to be used like a closure but I'm not sure why it isn't consistently re-initialized (except that we never explicitly declare it again.)

        Perhaps you disagree that a "named closure" is a closure at all and I suppose that is debatable. I think a "named closure" fits the definition that I gave above though, so I'm in agreement with broquaint when he says that he doesn't think the Camel (3rd edition) is entirely accurate in its "anonymous function" requirement.

        "My two cents aren't worth a dime.";
Re: Closure on Closures
by bhappy (Scribe) on Jan 29, 2004 at 07:49 UTC
    is it some special kind of variable on the 10th line of our last example? it doesn't look like other variables i used to. and i was not able to find anything like that in perlvar.
      is it some special kind of variable on the 10th line of our last example?
      10: $::PROGRAM = basename $0;

      You don't indicate which of the two variables in that line that you haven't encountered before.

      1. $::PROGRAM

        This is a package global variable in main. $::var is equivalent to $main::var.

        See one of broquaint's other tutorial's Of Symbol Tables and Globs for more information if that was the source of your question.

        The all upper case is a loose convention used to indicate global variables.

      2.  $0

        This is documented in perlvar. Search for the text "$PROGRAM_NAME".


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
      Timing (and a little luck) are everything!

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://268891]
Front-paged by diotalevi
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (8)
As of 2024-05-20 16:20 GMT
Find Nodes?
    Voting Booth?

    No recent polls found