Home > Net >  Closures vs. local `sub`s
Closures vs. local `sub`s

Time:06-18

Perl 5.18.2 accepts "local subroutines", it seems.

Example:

sub outer()
{
    my $x = 'x';   # just to make a simple example

    sub inner($)
    {
        print "${x}$_[0]\n";
    }

    inner('foo');
}

Without "local subroutines" I would have written:

#...
    my $inner = sub ($) {
        print "${x}$_[0]\n";
    }

    $inner->('foo');
#...

And most importantly I would consider both to be equivalent.

However the first variant does not work as Perl complains:

Variable $x is not available at ...

where ... describes the line there $x is referenced in the "local subroutine".

Who can explain this; are Perl's local subroutines fundamentally different from Pascal's local subroutines?

CodePudding user response:

The term "local subroutine" in the question seems to be referring to lexical subroutines. These are private subroutines visible only within the scope (block) where they are defined, after the definition; just like private variables.

But they are defined (or pre-declared) with my or state, as my sub subname { ... }

Just writing a sub subname { ... } inside of another doesn't make it "local" (in any version of Perl), but it is compiled just as if it were written alongside that other subroutine and is placed in their package's symbol table (main:: for example).

CodePudding user response:

If you want "local" subs, you can use one of the following based on the level of backward compatibility you want:

  • 5.26 :

    my sub inner { ... }
    
  • 5.18 :

    use experimental qw( lexical_subs );  # Safe: Accepted in 5.26.
    
    my sub inner { ... }
    
  • "Any" version:

    local *inner = sub { ... };
    

However, you should not, use sub inner { ... }.


sub f { ... }

is basically the same as

BEGIN { *f = sub { ... } }

so

sub outer {
   ...

   sub inner { ... }

   ...
}

is basically

BEGIN {
   *outer = sub {
      ...

      BEGIN {
         *inner = sub { ... };
      }

      ...
   };
}

As you can see, inner is visible even outside of outer, so it's not "local" at all.

And as you can see, the assignment to *inner is done at compile-time, which introduces another major problem.

use strict;
use warnings;
use feature qw( say );

sub outer {
   my $arg = shift;

   sub inner {
      say $arg;
   }

   inner();
}

outer( 123 );
outer( 456 );
Variable "$arg" will not stay shared at a.pl line 9.
123
123

5.18 did introduce lexical ("local") subroutines.

use strict;
use warnings;
use feature qw( say );
use experimental qw( lexical_subs );  # Safe: Accepted in 5.26.

sub outer {
   my $arg = shift;

   my sub inner {
      say $arg;
   };

   inner();
}

outer( 123 );
outer( 456 );
123
456

If you need to support older versions of Perl, you can use the following:

use strict;
use warnings;
use feature qw( say );

sub outer {
   my $arg = shift;

   local *inner = sub {
      say $arg;
   };

   inner();
}

outer( 123 );
outer( 456 );
123
456

CodePudding user response:

I found a rather good explanation from man perldiag:

       Variable "%s" is not available
           (W closure) During compilation, an inner named subroutine or eval
           is attempting to capture an outer lexical that is not currently
           available.  This can happen for one of two reasons.  First, the
           outer lexical may be declared in an outer anonymous subroutine
           that has not yet been created.  (Remember that named subs are
           created at compile time, while anonymous subs are created at run-
           time.)  For example,

               sub { my $a; sub f { $a } }

           At the time that f is created, it can't capture the current value
           of $a, since the anonymous subroutine hasn't been created yet.

So this would be a possible fix:

sub outer()
{
    my $x = 'x';   # just to make a simple example

    eval 'sub inner($)
    {
        print "${x}$_[0]\n";
    }';

    inner('foo');;
}

...while this one won't:

sub outer()
{
    my $x = 'x';   # just to make a simple example

    eval {
        sub inner($)
        {
            print "${x}$_[0]\n";
        }
    };

    inner('foo');;
}
  • Related