closures.pl (3446B) - raw
1 #!/usr/local/bin/perl 2 3 use strict; 4 use warnings; 5 use v5.32; 6 7 # From Chapter 7 of "Beginning Perl" by Curis "Ovic" Poe: 8 # The make_fibonacci() subroutine returns an anonymous subroutine that 9 # references the $current and $next variables declared in the 10 # make_fibonacci() subroutine, but outside of the anonymous subroutine. 11 # The $iterator variable contains a reference to this anonymous subroutine, 12 # and it “remembers” the values of the $current and $next variables. Every 13 # time it is invoked, it updates the values of $current and $next and 14 # returns the next Fibonacci number. Eventually, you get to the for loop 15 # that prints the first 10 Fibonacci numbers. You can pass the $iterator 16 # variable to other subroutines just like any other variable, and it still 17 # remembers its state. You can create several iterators with this same 18 # subroutine, and each will have a separate copy of $current and $next. 19 20 sub make_fibonacci { 21 my ( $current, $next ) = ( 0, 1 ); 22 return sub { 23 my $fibonacci = $current; 24 ( $current, $next ) = ( $next, $current + $next ); 25 return $fibonacci; 26 } 27 } 28 29 my $iterator = make_fibonacci(); 30 31 for ( 1 .. 10 ) { 32 my $fibonacci = $iterator->(); 33 say $fibonacci; 34 } # 0, 1, 1, 2, ..., 21, 34 35 36 say $iterator->(); # 55 37 38 say "--------------------"; 39 40 my $iterator2 = make_fibonacci(); 41 say $iterator2->() for ( 1 .. 5 ); # 0, 1, 1, 2, 3 42 43 say "--------------------"; 44 45 # The example below is taken from Chapter 2 of "Object Oriented Perl" by 46 # Damian Conway. 47 # When the hop_along subroutine is called, it copies its three arguments 48 # into lexical variables, sets up another lexical (i.e., $next) as a counter, 49 # and then creates an anonymous sub-routine that uses the values in those 50 # variables. The anonymous subroutine increments $next by $step each time 51 # the subroutine is called, until the result is greater than $to. A reference 52 # to the newly created anonymous subroutine is then returned. 53 54 sub hop_along { 55 my ( $from, $to, $step ) = @_; # unpack arguments 56 my $next = $from - $step; # initialize counter 57 my $closure_ref = 58 sub { 59 60 # the first time we call the iterator $closure_ref, $next is equal to 61 # $from - $step as defined above. In subsequent calls, increments according 62 # to $next += $step. 63 $next += $step; # take a step 64 return if ( $next > $to ); # we've reached the end 65 # the next line sets the calling parameter $_[0] to equal to the $next that 66 # is in the scope of this array. $_[0] is set as the calling parameter 67 # which is transferred to the $next down below in the calling routine. 68 $_[0] = $next; # otherwise, set new value 69 return 1; # exit 70 }; 71 return $closure_ref; # return closure 72 } 73 74 # Normally, the lexical variables inside the call to hop_along (i.e., $from, 75 # $to, $step, $next) would cease to exist when hop_along returned. But, 76 # because the anonymous subroutine still needs access to them, Perl arranges 77 # for them to live on in seclusion, accessible only by the anonymous 78 # subroutine itself. Closures are a means of giving a subroutine its own 79 # private memory — variables that persist between calls to that subroutine 80 # and are accessible only to it. 81 82 my $iterator3 = hop_along (1, 30, 7); # Create closure 83 my $next; 84 while ($iterator3->($next)) # Call closure 85 { 86 say $next; 87 }