Search code examples
perlanonymous-function

Best way to Immediately Invoked Function Expression (IIFE) in perl?


Borrowing the the term form a Javascript, what is the 'best practice' way to IIFE in perl?

My test code below is a simple loop calling anonymous sub and executing it right away to build an array of subs (which just return the loop index). It is mostly what I want, however I'm needing to use an intermediate variable ( instead of using @_, which changes on the internal function).

use warnings;
use strict;
my @funcs;
for(my $i=0;$i<3;$i++) {
    sub  {
        my $index=shift;
        $funcs[$index]=sub {$index};
    }
    -> ($i);
}

for (@funcs) {
    print &$_()."\n";
}
#Output
0
1
2

I know I can use map to restructure this problem. But putting that aside, is there a better way to do this?

Update

Thanks to @ikegami for highlighting some important points.

Just for future views of this question, my thoughts on this:

The 'iterator' for loop has different scoping (is it a map?) than a 'c style' for loop. That cleans up the code with out needing an IIFE at all. Sweet.

Update 2

Following code shows the differences I'm seeing. Not saying one is better than the other but good to know I think. The output I'm after is 0 1 2 but the first one only repeats the last value of $i (3 after the ++ operator).

use warnings;
use strict;
my @funcs;
print "C loop direct assignment of sub\n";
for(my $i=0;$i<3;$i++) {
    $funcs[$i]= sub {$i};
}
print &$_()."\n" for @funcs;

print "C loop direct assignment of sub with variable\n";
for(my $i=0;$i<3;$i++) {
    my $index=$i; #assignment/copy
    $funcs[$index]= sub {$index};
}
print &$_()."\n" for @funcs;

print "For loop interator\n";
@funcs=[];
for my $i (0..2) {
    $funcs[$i]=sub {$i};
}
print &$_()."\n" for @funcs;

print "C loop with IIFE assignment\n";
@funcs=[];
for (my $i=0;$i<3;$i++) {
    sub  {
    my $index=shift;
        $funcs[$index]=sub {$index};
    }
    -> ($i);
}
print &$_()."\n" for @funcs;

Out is:

C loop direct assignment of sub                                                                                                                                          
3                                                                                                                                                                        
3                                                                                                                                                                        
3         
C loop direct assignment of sub with variable                                                                                                                            
0                                                                                                                                                                        
1                                                                                                                                                                        
2                                                                                                                                                                            
For loop interator                                                                                                                                                       
0                                                                                                                                                                        
1                                                                                                                                                                        
2                                                                                                                                                                        
C loop with IIFE assignment                                                                                                                                                           
0                                                                                                                                                                        
1                                                                                                                                                                        
2        

Solution

  • The Perl equivalent of

    (function () {
       var x = ...;
       ...
    })();
    

    is

    sub {
       my $x = ...;
       ...
    }->();
    

    That said, the IIFE is just a workaround that's simply not needed in Perl.

    (function () {
       var x = ...;
       ...
    })();
    

    is a workaround for

    {
       my $x = ...;
       ...
    }
    

    and

    var result = (function () {
       return ...;
    })();
    

    is a workaround for

    my $result = do {
       ...
    };
    

    It looks like you are trying to translate something similar to the following:

    let funcs = [];
    for ( let i=0; i<3; ++i ) {
       (function() {
          var index = i;
          funcs.push( function() { return index; } );
       })();
    }
    
    for ( let func of funcs )
       console.log( func() );
    

    The following is the direct translation:

    my @funcs;
    for ( my $i=0; $i<3; ++$i ) {
       sub {
          my $index = $i;
          push @funcs, sub { $index };
       }->();
    }
    
    say $_->() for @funcs;
    

    But there's simply no point in using an IIFE. One would simply use the following:

    my @funcs;
    for ( my $i=0; $i<3; ++$i ) {
       my $index = $i;
       push @funcs, sub { $index };
    }
    
    say $_->() for @funcs;
    

    Now, one tends to avoid C-style for loops in Perl because using foreach loops are far more readable (and more efficient!). It also makes the solution even simpler because the loop variable of a foreach loop is scoped to the body of the loop rather than scoped to the loop statement.

    my @funcs;
    for my $i ( 0 .. 2 ) {
       push @funcs, sub { $i };
    }
    
    say $_->() for @funcs;
    

    We could also use map.

    my @funcs = map { my $i = $_; sub { $i } } 0 .. 2;
    
    say $_->() for @funcs;