Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl: "Variable will not stay shared"

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.

like image 924
jackthehipster Avatar asked Aug 20 '14 08:08

jackthehipster


2 Answers

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.

like image 187
ysth Avatar answered Nov 10 '22 08:11

ysth


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
like image 8
perreal Avatar answered Nov 10 '22 08:11

perreal