Creating an alias to an existing hash (from a hash reference)

J

jl_post

Hi,

Recently I was reviewing someone's Perl code and I saw that his
code used a few global variables that were set and accessed by several
functions. Since I am not a fan of global variables, I edited the
code to get rid of all but one of them.

(For the record, his code used "strict" and "warnings".)

The remaining global variable was a hash. I proceded to edit the
code so that the functions took a hash reference as an input
argument. So previously, the code looked something like:

my %hash;

{
f1();
f2();
f3();
f4();
}

sub f1 { ... }
sub f2 { ... }
sub f3 { ... }
sub f4 { ... }

(This is a simplification.)

I changed those lines to something like:

sub f1 { ... }
sub f2 { ... }
sub f3 { ... }
sub f4 { ... }

{
my %hash;

f1(\%hash);
f2(\%hash);
f3(\%hash);
f4(\%hash);
}

In the function definitions themselves, I added code to read in the
input arguments, like this:

sub f
{
my ($hashRef) = @_;
 
C

C.DeRykus

...
   However, since his code had "use strict;" in effect, "perl -c"
responded with:

Variable "%hash" is not imported at ...

So I declared %hash the line before with "my", like this:

   my %hash;
   local *hash = $hashRef;

   That didn't work; although it compiled just fine, %hash was empty.
So then I changed it to be declared with "our" instead of "my", like
this:

   our %hash;
   local *hash = $hashRef;

and that worked.  From then on in that function, every instance of
%hash pointed back to the passed-in %hash (that was passed in as a
reference).

I was glad to hear about Data::Alias which's the way to go.
However, 'no strict', used sparingly, may make sense in some
cases too, particularly as an 'our' alternative:

{ no strict; local *hash = $hashRef; $hash{...} = ...; }
 
J

jl_post

On Dec 17, 2:52 pm, "(e-mail address removed)" <[email protected]> > I wasglad to hear about Data::Alias which's the way to go.
However, 'no strict', used sparingly, may make sense in some
cases too, particularly as an 'our' alternative:

{ no strict; local *hash = $hashRef; $hash{...} = ...; }


Actually, I'm pretty sure that, when "no strict" is in effect, all
non-declared variables are automatically treated as package variables
(that is, as if they were declared with "our").

So it's not really an alternative to "our"; instead, that's all
you'd be using (unless, of course, you explicitly use "my").

(I tested it out with the following example:

1. I started the Perl interpreter with: perl -wde 1

2. Then I typed:

use strict; { no strict; *blah = \%ENV; }

3. Then I was able to reference %ENV through %blah outside
of the scope %blah was fist used in, like this:

print $blah{PATH};

So disabling "strict" doesn't avoid the hazards of using "our",
unfortunately.)

And I agree with you that Data::Alias is definitely the way to go.
I just wish it was a standard module so it could be used without
having to install it explicitly.

-- Jean-Luc
 
J

jl_post

    use Data::Alias;

    sub f {
        my ($hashRef) = @_;
        alias my %hash = %$hashref;
        ...;
    }

Thanks, Ben, yet again! I just installed Data::Alias from CPAN,
and it works great.

It would be nice if it were a standard module, so I could freely
use it on code that is supposed to run on plain Perl installations
(where I have no control over which Perl modules are installed).

If I find myself in such a situation in the future (which I'm sure
will happen), I'll probably just resort to search-and-replacing
instances of "$hash{" to "$hashRef->{".

But if I'm in a situation where I'm free to use CPAN modules, I'll
probably be making good use of Data::Alias.

Thanks again, Ben.

-- Jean-Luc
 
J

jl_post

Oh.. - why do you say that?


Because when you make a copy of a hash, a completely separate copy
is made. Consider this code:

my %hash = %ENV;

or even:

my $hashRef = \%ENV;
my %hash = %$hashRef;

With either approach, %hash is now equivalent to %ENV, but only
temporarily, since it is a completely separate hash. If you add a new
entry to %hash, like this:

$hash{apple} = 'banana';

that same entry won't exist in %ENV. Likewise, if %ENV is modified
(for whatever reason), that modification won't carry over to %hash.

I hope this clarifies things, Ruben.

-- Jean-Luc
 
C

C.DeRykus

   Actually, I'm pretty sure that, when "no strict" is in effect, all
non-declared variables are automatically treated as package variables
(that is, as if they were declared with "our").

No, ordinary package variables and those declared with
'our' are scoped differently. And 'our' can be clobbered
in a multi-package global way that ordinary package
variables won't be:

perl -Mstrict -wle "package main; our $x=1;
package Foo; our $x=2;
package main; print $x"
2

vs.

perl -Mstrict -wle "package main; use vars '$x';$x=1;
package Foo; use vars '$x';$x=2;
package main; print $x"
1

   So it's not really an alternative to "our"; instead, that's all
you'd be using (unless, of course, you explicitly use "my").

   (I tested it out with the following example:

1. I started the Perl interpreter with:  perl -wde 1

2. Then I typed:

      use strict; { no strict; *blah = \%ENV; }

3. Then I was able to reference %ENV through %blah outside
   of the scope %blah was fist used in, like this:

      print $blah{PATH};

   So disabling "strict" doesn't avoid the hazards of using "our",
unfortunately.)

Hm, you didn't declare 'blah' or localize inside the
{no strict;...} so that won't compile outside of the
debugger:

perl -Mstrict -le "{no strict; *blah = \%ENV;}
print $blah{PATH}"
Variable "%blah" is not imported ...
Global symbol "%blah" requires explicit package name...


But with those corrections, the first print below
works as expected and the final print outside the
{no strict;...} draws an uninitialized warning:


perl -Mstrict -wle "use vars '%blah';
{no strict; local *blah = \%ENV; print $blah{PATH};}
print $blah{PATH}"
 
X

Xho Jingleheimerschmidt

If I find myself in such a situation in the future (which I'm sure
will happen), I'll probably just resort to search-and-replacing
instances of "$hash{" to "$hashRef->{".

Don't forget about replacing "@hash{" with "@{$hashRef}{", and "keys
%hash" with "keys %$hashRef", etc.

Xho
 
J

jl_post

the first print below
works  as expected and the final print outside the
{no strict;...} draws an uninitialized warning:

perl -Mstrict -wle "use vars '%blah';
      {no strict; local *blah = \%ENV; print $blah{PATH};}
      print $blah{PATH}"


True, but the uninitialized warning happened because you used the
"local" keyword. If you remove that, then the second print() line
will have access to %ENV via %blah.

So you might think that using "local" prevents code in outside
scopes from accessing %ENV through %blah. But that's not true. The
"local" keyword temporarily changes the value of %blah, but it changes
it everywhere in the package, not just in its scope.

Consider this code:

perl -Mstrict -wle "{no strict; local *blah = \%ENV; f() }
sub f { our %blah; print $blah{PATH} }

Even though the code inside f() is in a completely different scope
that the code that calls f(), the code inside f() still has access to
%blah as the calling code sees it (and therefore has access to modify
%ENV through it).

Sure, declaring *blah as "local" in f() would prevent that from
happening, but since you can't always guarantee that all code will
"local"ize all its typeglobs, declaring one typeglob as "local" won't
necessarily stop all code (outside the intended scope) from accessing
what %blah references. (It'll stop some code, but not all code.)

Cheers,

-- Jean-Luc
 
J

jl_post

No, ordinary package variables and those declared with
'our' are scoped differently. And 'our' can be clobbered
in a multi-package global way that ordinary package
variables won't be:

  perl -Mstrict -wle "package main; our $x=1;
                      package Foo;  our $x=2;
                      package main; print $x"
  2

vs.

  perl -Mstrict -wle "package main; use vars '$x';$x=1;
                      package Foo;  use vars '$x';$x=2;
                      package main; print $x"
  1


Interesting. I did not know that.

"perldoc -f our" helped me understand a little more.

Of course, if your scopes don't cross package boundaries, then this
isn't an issue to worry about. But if they do, then this is good to
know.

-- Jean-Luc
 
C

C.DeRykus

   True, but the uninitialized warning happened because you used the
"local" keyword.  If you remove that, then the second print() line
will have access to %ENV via %blah.

True but it can be a good idea in general to
insulate a variable with 'local' to avoid
avoid clobbering any potential existing values.
I thought you had control of that part of the
code...
   So you might think that using "local" prevents code in outside
scopes from accessing %ENV through %blah.  But that's not true.  The
"local" keyword temporarily changes the value of %blah, but it changes
it everywhere in the package, not just in its scope.

   Consider this code:

   perl -Mstrict -wle "{no strict; local *blah = \%ENV; f() }
                       sub f { our %blah; print $blah{PATH} }

Even though the code inside f() is in a completely different scope
that the code that calls f(), the code inside f() still has access to
%blah as the calling code sees it (and therefore has access to modify
%ENV through it).

   Sure, declaring *blah as "local" in f() would prevent that from
happening, but since you can't always guarantee that all code will
"local"ize all its typeglobs, declaring one typeglob as "local" won't
necessarily stop all code (outside the intended scope) from accessing
what %blah references.  (It'll stop some code, but not all code.)

That's expected because 'local's dynamic scoping
propagates the localized variable into any called
subroutine in the localized scope. For instance:

{ local $foo='bar'; f(); }

"The f() or any sub called within f() all the way
down the call stack will see $foo with its local
value 'bar'.

So, used correctly, 'local' does limit what's seen.
There's quite a bit of confusion about the gory
details of 'local' however. And details in perlsub
and perlfaq7 are skimpy..
 
C

C.DeRykus

Quoth "C.DeRykus" <[email protected]>:




What about local's behaviour is unclear to you?

Plenty :) but I just intending to say that there's
more to 'local' than a quick peek at the docs might
show. There're some caveats/subtleties to 'local'.
as well as the need for understanding 'my vs. local',
and 'lexical vs. dynamic' scoping issues, etc.. Even
the word 'local' is a bit of a mis-nomer. Lots of
good info in perlsub but close, careful look(s) are
needed. Fun for young and old alike...

I can't find it now but there was a lengthy thread I just
saw yesterday (and now can't find) that dealt with what
appeared to be buggy behavior of local. I think Abigail
demo'ed a short code example to make the case for a bug.
Others, arguably more correct, countered the "bug' was
just a 'feature' due to the way 'local' was implemented.
 
C

C.DeRykus

...
I can't find it now but there was a lengthy thread I just
saw yesterday (and now can't find)  that dealt with what
appeared to be buggy behavior of local. I think Abigail
demo'ed a short code example to make the case for a bug.
Others, arguably more correct, countered the "bug' was
just a 'feature' due to the way 'local' was implemented.

Still can't find the thread but I think
the code was kinda/sorta like:

use strict;
use warnings;
use vars qw/$foo $bar/;
$foo = 3;
$bar = \$foo;
{
local $foo = 4;
print $$bar; # 3 not 4!
}
 
M

Marc Girod

there are actually
three levels: 'name', 'container' and 'value'.

Excellent discussion, which could be generalized
to a critique of the concept of 'high-level'.

High-level means: 'simpler on the surface'.
Low-level means: 'simpler at depth'.

Weren't you explaining a high-level concept
by the means of its (simpler) low-level
implementation?

Marc
 
C

C.DeRykus

Quoth "C.DeRykus" <[email protected]>:



'local' takes the name you pass it, records which container it points to
to be restored at the end of the scope, and creates a new container for
the name to point to until then. (This is why 'local' variables start
out empty.) When you say

    $bar = \$foo;

above, you are putting a reference to the *current* container for $foo
into $bar; this reference is now completely divorced from the name
'$foo' and is unaffected by 'local' pointing that name somewhere else.

Thanks, great explanation.

And addresses confirm that the localized address
is now 'somewhere else':

perl -wle "no strict; $foo='foobar'; $bar=\$foo; print \$foo;
{ local $foo = 'foobar1'; print \$foo; }"
SCALAR(0x16808e4)
SCALAR(0x2b7b14)
Note that this is only true of Perl 5-style hard refs: Perl 4-style
symrefs point to a name (after all, they're only strings) so

    no strict;
    $foo = 3;
    $bar = "foo";
    {
        local $foo = 4;
        say $$bar;
    }

will see the localized value for $foo, as you might have expected.

Good 'ole slow, straightforward symrefs ;)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,737
Latest member
Georgeengab

Latest Threads

Top