wsppan has asked for the wisdom of the Perl Monks concerning the following question:

This baffles me how this works. I am also curious as to why you would use this technique and how long this technique has been available.
use strict; use warnings; use 5.010; use feature qw(say); sub outer_sub { say "Outer Subroutine Start ..."; my $param = 0; say "PARAM: $param"; $param = inner_sub($param); say "PARAM: $param"; say "Outer Subroutine End."; sub inner_sub { my $param = shift; say "Start inner_sub1"; return ++$param; } } # Main outer_sub(); exit(0); __END__ Outer Subroutine Start ... PARAM: 0 Start inner_sub1 PARAM: 1 Outer Subroutine End.

Replies are listed 'Best First'.
Re: Inner subroutines?
by BrowserUk (Patriarch) on Feb 10, 2011 at 22:36 UTC

    Effectively, that code is identical to this:

    use strict; use warnings; use 5.010; use feature qw(say); sub inner_sub { my $param = shift; say "Start inner_sub1"; return ++$param; } sub outer_sub { say "Outer Subroutine Start ..."; my $param = 0; say "PARAM: $param"; $param = inner_sub($param); say "PARAM: $param"; say "Outer Subroutine End."; } # Main outer_sub(); exit(0); __END__ [22:16:28.33] c:\test>junk41 Outer Subroutine Start ... PARAM: 0 Start inner_sub1 PARAM: 1 Outer Subroutine End.

    The fact that inner_sub() is enclosed in outer_sub() makes no difference to its visibility:

    use strict; use warnings; use 5.010; use feature qw(say); sub outer_sub { say "Outer Subroutine Start ..."; my $param = 0; say "PARAM: $param"; $param = inner_sub($param); say "PARAM: $param"; say "Outer Subroutine End."; sub inner_sub { my $param = shift; say "Start inner_sub1"; return ++$param; } } # Main outer_sub(); inner_sub(); ### Visible here also! exit(0); __END__ [22:20:34.37] c:\test>junk41 Outer Subroutine Start ... PARAM: 0 Start inner_sub1 PARAM: 1 Outer Subroutine End. Start inner_sub1

    But, declaring nested sub is "dangerous" in that it can result in closures that don't work properly like this:

    use strict; use warnings; use 5.010; use feature qw(say); sub outer_sub { my $scoped_var = 'fred'; ## closure say "Outer Subroutine Start ..."; my $param = 0; say "PARAM: $param"; $param = inner_sub($param); say "PARAM: $param"; say "Outer Subroutine End."; sub inner_sub { my $param = shift; say "Start inner_sub1"; say $scoped_var; ## closure return ++$param; } } # Main outer_sub(); inner_sub(); exit(0); __END__ [22:22:00.66] c:\test>junk41 Variable "$scoped_var" will not stay shared at C:\test\junk41.pl line +20. Outer Subroutine Start ... PARAM: 0 Start inner_sub1 fred PARAM: 1 Outer Subroutine End. Start inner_sub1 fred

    Though I forget the circumstances in which the "Variable "$scoped_var" will not stay shared " actually causes a problem. I think it's been possible to declare inner subs for a long time, certainly as long as I've been using Perl, but AFAIK, there is no good reason for doing so, and it can--though I forget the circumstances because I never do it--cause problems.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Inner subroutines?
by ikegami (Patriarch) on Feb 10, 2011 at 22:34 UTC

    The "inner" sub is actually visible to everyone.

    $ perl -wE'sub o { sub i { say "foo"; } } i();' foo

    The "inner" sub cannot use variables from the "outer" sub.

    $ perl -wE'sub o { my ($x) = @_; sub i { say $x; } i(); } o("abc"); o( +"def");' Variable "$x" will not stay shared at -e line 1. abc abc

    I tend to call what you have a bug, not a technique.

      It's one of my major peeves with Perl5: that inner subs don't work it is as reasonable to expect. The way they work in Javascript, for example.

      It's the reason why you cannot use "my" variables in the top level of source files for mod_perl, when using Apache::Registry, the easy route from CGI to mod_perl.

      There are articles on how to fix variables for inner subroutines, but the fact that you even need to worry about it is a major counterpoint against Perl, in my opinion.

      The reason, as far as I gather, is because BEGIN (and related) blocks are actually subs, that are executed once; you can even use the syntax

      sub BEGIN { ... }
      and subs defined in BEGIN blocks are global subs.

      And, Larry Wall and other prominent figures have said, years ago, that it'll never be "fixed". Because it is so by design.

        It's got nothing to do with BEGIN being sub-like. Inner subs would have to capture every time the outer sub is called. But since there are no private subs in Perl, there isn't necessarily any executing outer sub to capture from.

        sub outer { sub inner { } }
        is basically
        sub outer { BEGIN { *inner = sub { }; } }
        Replace it with
        sub outer { *inner = sub { }; }

        and inner will capture properly for an inner sub. Of course, problems will still ensue if you call inner() from outside of outer(). To solve that, you'd need a language feature such as

        sub outer { my sub inner { } }
Re: Inner subroutines?
by Anonymous Monk on Feb 10, 2011 at 22:19 UTC
Re: Inner subroutines?
by 7stud (Deacon) on Feb 10, 2011 at 23:30 UTC

    Named subroutine definitions are global--no matter where you put them in your code. On the other hand, anonymous subroutines (i.e. closures) can see the variables in the scope in which they are defined:

    my @coderefs; for my $num (10, 20, 30) { my $ref = sub {print "$num\n"}; push @coderefs, $ref; } #Note: $num has ceased to exist here... #...yet the following subs can still see the value of #$num at the time they were created: $coderefs[0]->(); $coderefs[2]->(); --output:-- 10 30
Re: Inner subroutines?
by blakew (Monk) on Feb 11, 2011 at 04:05 UTC
    I've found scoping named subroutines useful when I want to initialize a data structure outside the subroutine but only visible to that one (or really as many as I want).
    ... { my @data = qw( this isn't visible elsewhere ); sub func { # Do something with @data here ... } } ...

    You could do just as well inside another subroutine instead of just a bare block (@data is private to both subroutines), then you're working with closures. Typically though the inner subroutine is spawned anonymously and returned from the outer sub. For a good example of that see List::MoreUtils's natatime.

    Seeing as how others have pointed out the inner sub is not really purposefully scoped (you redeclare your variable with 'my'), I'm guessing at what you're asking about. Higher Order Perl is a good book and one that I've been meaning to finish which talks all about closures and other things you can do with higher order functions.

      You could do just as well inside another subroutine instead of just a bare block

      I'm not seeing that:

      use warnings; use strict; use warnings; use strict; sub outer { my $data = 10; sub test { print $data; } } outer(); test(); --output:-- Variable "$data" will not stay shared at t.pl line 8. 10

      Nor here:

      use warnings; use strict; sub outer { my $data = 10; sub test { print $data; } test(); } outer(); --output:-- Variable "$data" will not stay shared at t.pl line 8. 10
        You're right it was a bad suggestion, I'm not sure what's going on here, it seems the variable isn't initialized until a call to outer; subsequent calls to outer() don't retain the initial reference but inner() does?
Re: Inner subroutines?
by ron7 (Beadle) on Feb 11, 2011 at 06:03 UTC
    As the others have said, don't do it (hard as that might be if coming from languages which do truly have scoped inner methods).

    The Perl Critic will highlight this as a "Severity 5" problem--the worst. If you ever want to be humbled, try passing your best code through it (http://perlcritic.org) or the cpan version. Fixing the code so it largely passes at the "Brutal" level taught me a lot.

Re: Inner subroutines?
by wsppan (Acolyte) on Feb 11, 2011 at 15:28 UTC
    Thank you everyone for your replies. I never wrote code like this myself and I always run my code through Perl::Tidy and Perl::Critic. I came across this while looking at the Testopia Bugzilla extension and was wondering if there was any reason why or if any black magic was going on.