24

I looked up a few answers dealing with this warning, but neither did they help me, nor do I truly understand what Perl is doing here at all. Here's what I WANT it to do:

sub outerSub {
  my $dom = someBigDOM;
  ...
  my $otherVar = innerSub();
  return $otherVar;

  sub innerSub {
    my $resultVar = doStuffWith($dom);
    return $resultVar;
  }
}

So basically, I have a big DOM object stored in $dom that I don't want to pass along on the stack if possible. In outerSub, stuff is happening that needs the results from innerSub. innerSub needs access to $dom. When I do this, I get this warning "Variable $dom will not stay shared".

What I don't understand:

  1. Does this warning concern me here? Will my intended logic work here or will there be strange things happening?

  2. If it doesn't work as intended: is it possible to do that? To make a local var visible to a nested sub? Or is it better to just pass it as a parameter? Or is it better to declare an "our" variable?

  3. If I push it as a parameter, will the whole object with all its data (may have several MB) be pushed on the stack? Or can I just pass something like a reference? Or is Perl handling that parameter as a reference all by itself?

  4. In "Variable $foo will not stay shared" Warning/Error in Perl While Calling Subroutine, someone talks about an anonymous sub that will make this possible. I did not understand how that works, never used anything like that.

  5. I do not understand that explanation at all (maybe cause English is not my first language): "When the inner subroutine is called, it will see the value of the outer subroutine's variable as it was before and during the first call to the outer subroutine; in this case, after the first call to the outer subroutine is complete, the inner and outer subroutines will no longer share a common value for the variable.":

What does "the first call to the outer subroutine is complete? mean"
I mean: first I call the outer sub. The outer sub calls the inner sub. The outer sub is of course still running. Once the outer sub is complete, the inner sub will be finished as well. Then how does any of this still apply when the inner sub is already finished? And what about the "first" call? When is the "second" call happening... sorry, this explanation confuses me to no end.

Sorry for the many questions. Maybe someone can at least answer some of them.

Community
  • 1
  • 1
jackthehipster
  • 978
  • 8
  • 26

4 Answers4

21

In brief, the second and later times outerSub is called will have a different $dom variable than the one used by innerSub. You can fix this by doing this:

{
    my $dom;
    sub outerSub {
        $dom = ...
        ... innerSub() ...
    }
    sub innerSub {
        ...
    }
}

or by doing this:

sub outerSub {
    my $dom = ...
    *innerSub = sub {
        ...
    };
    ... innerSub() ...
}

or this:

sub outerSub {
    my $dom = ...
    my $innerSub = sub {
        ...
    };
    ... $innerSub->() ...
}

All the variables are originally preallocated, and innerSub and outerSub share the same $dom. When you leave a scope, perl goes through the lexical variables that were declared in the scope and reinitializes them. So at the point that the first call to outerSub is completed, it gets a new $dom. Because named subs are global things, though, innerSub isn't affected by this, and keeps referring to the old $dom. So if outerSub is called a second time, its $dom and innerSub's $dom are in fact separate variables.

So either moving the declaration out of outerSub or using an anonymous sub (which gets freshly bound to the lexical environment at runtime) fixed the problem.

ysth
  • 96,171
  • 6
  • 121
  • 214
  • Thx for clarifying. Does that mean that once innerSub accesses $dom, it somehow destroys it? What is innerSub seeing in the subsequent calls, if not the same $dom as outerSub? – jackthehipster Aug 20 '14 at 08:51
  • Great explanation. Althoug in my opinion, Perl is doing something not right here. Since the named sub is declared inside a block, it should not be a global thing, but only "global to the block", in other words local. – jackthehipster Aug 21 '14 at 08:30
  • 1
    Don't forget to add a semicolon at the end of the anonymous sub (after the obvious changes at the beginning). – Wolf May 29 '19 at 09:12
  • 1
    I think the key to understand this is that innerSub isn't really "inner" in any sense - it's on the same level as the outerSub - with the exception of $dom being in scope at the time of definition. So innerSub and outerSub do not share $dom, as outerSub will have a new version of it on every call, while innerSub will simply refer to the outer $dom that was in scope at definition time - they are not shared. – Remember Monica Nov 28 '21 at 16:49
8

You need to have an anonymous subroutine to capture variables:

my $innerSub = sub  {
  my $resultVar = doStuffWith($dom);
  return $resultVar;
};

Example:

sub test {
    my $s = shift;

    my $f = sub {
        return $s x 2;
    };  

    print $f->(), "\n";

    $s = "543";

    print $f->(), "\n";
}

test("a1b");

Gives:

a1ba1b
543543
perreal
  • 94,503
  • 21
  • 155
  • 181
  • 1
    May I ask: why? Ok, that's the way it is, but why isn't it working as expected, and why is it working with anonymous subs? – jackthehipster Aug 20 '14 at 12:06
  • 1
    Here's my attempt/guess at explaining this :-) The compiler deals with the anonymous sub differently. In your example the return value of a subroutine/function is assigned to a scalar. With the code reference or anonymous subroutine the code is run when it is called and as it is defined. Scoping is then more "granular". A couple of classic explanations: http://www.perlmonks.org/?node_id=66677 (Perl Monks node 66677) and http://www.plover.com/~mjd/perl/FAQs/Namespaces.html (Coping with Scoping) really help grok this feature. – G. Cito Aug 20 '14 at 13:39
5

If you want to minimize the amount of size passing parameters to subs, use Perl references. The drawback / feature is that the sub could change the referenced param contents.

my $dom = someBigDOM;
my $resultVar = doStuffWith(\$dom);


sub doStuffWith {
   my $dom_reference = shift;
   my $dom_contents = $$dom_reference;
   #...
}
Miguel Prz
  • 13,718
  • 29
  • 42
  • 1
    A collegue told me just yesterday that Perl will pass references by default. So calling doStuffWith($dom) should actually only put a reference on the stack. That would be consistent with the fact that if the passed variable is changed inside the sub, the original var is changed. But I don't know what Perl is really doing, it might just do some weird stuff that only makes it look like a ref was passed. – jackthehipster Aug 21 '14 at 07:22
  • 2
    No, params are passed by copy. If you want a reference you have to use explicit Perl reference (with backslash) – Miguel Prz Aug 21 '14 at 07:30
  • Thx Miguel. And do you know why it is that changing the value of a passed var in a sub will change the original var then? I'm trying hard to understand how Perl ticks... – jackthehipster Aug 21 '14 at 07:32
  • This http://modperlbook.org/html/14-2-5-Passing-Variables.html page says that dereferencing a parameter inside the sub makes little sense as it copies the contents of the original var to a new memory block. So from that I gather, either use the ref directly inside the sub by always saying "$$dom_refernce = ...", or don't pass it as ref at all. – jackthehipster Aug 21 '14 at 08:27
  • The `perldoc perlsub` make it clearer: `The array @_ is a local array, but its elements are aliases for the actual scalar parameters. In particular, if an element $_[0] is updated, the corresponding argument is updated (or an error occurs if it is not possible to update)...` So yes, they are passed as "copy", but internally still as ref. The copy only takes place once you `shift` them out of `@_`. That's why it's possible to change the original var inside the sub by using `@_[0] = ...`. – jackthehipster Aug 21 '14 at 08:34
  • If the param is a scalar (number or string) you cannot modify it that way – Miguel Prz Aug 21 '14 at 08:41
  • Actually with scalar vars, I can, I just tried. But if you mean a *literal* number or string param, then of course you are right. – jackthehipster Aug 21 '14 at 09:12
0

Following http://www.foo.be/docs/perl/cookbook/ch10_17.htm , you should define a local GLOB as follows :

local *innerSub = sub {
    ...
} 
#You can call this sub without ->
innerSub( ... ) 

Note that even if warning is displayed, the result stay the same as it should be expected : variables that are not defined in the inner sub are modified in the outer sub scope. I cannot see what this warning is about.

MUY Belgium
  • 2,330
  • 4
  • 30
  • 46