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

Hi all,

In the code below, I pass a hash reference "hashref" to a sub (pass1). This sub localises (using "my") the hashref and changes the values of num1 and num2.

I am curious why changing the values of num1 and num2 inside pass1 affects the values of num1 and num2 in hashref.

How do I preserve the values of hashref whilst making changes in pass1?

my $hashref = { num1 => 1, num2 => 2 }; print "before pass1: $hashref->{num1}\n"; #prints 1 pass1({ params => $hashref }); print "after pass1: $hashref->{num1}\n"; #prints 2 but would like it t +o stay as 1 sub pass1 { my $self = shift; my $params = $self->{params}; foreach my $key (keys %{ $params }) { $params->{$key}++; } print "Inside pass1...\n"; print "$params->{num1}\n"; # prints 2 correct print "$params->{num2}\n"; # prints 3 correct }

Am I missing something obvious?

Replies are listed 'Best First'.
Re: How to preserve values outside a sub
by AnomalousMonk (Archbishop) on Oct 25, 2017 at 17:25 UTC

    Further to RMGir here:

    If you want to pass a local copy, specify that:

    pass1({ params => {%$hashref} });  # {%$hashref} makes a copy of the hash '$hashref'
                                       # points to and creates a new ref to it.

    ... and stevieb here:

    You need to "copy" the reference in order to break the link:

    my $params = { %{ $self->{params} } };

    There's a pitfall here. Be aware that the "copy" above is only a shallow copy. Any references nested within the referent of the reference being shallow-copied still allow complete read/write access to their referents.

    c:\@Work\Perl\monks>perl -wMstrict -MData::Dump -le "my $hashref = { foo => { bar => 'nice' } }; dd 'before pass1: ', $hashref; ;; pass1({ params => { %$hashref } }); ;; dd 'after pass1: ', $hashref; ;; ;; sub pass1 { my $self = shift; my $params = $self->{params}; dd 'in pass1, before assignments ', $params; ;; $params->{fizz} = 'fazz'; $params->{foo}{bar} = 'KaPow'; dd 'in pass1 after assignments ', $params; } " ("before pass1: ", { foo => { bar => "nice" } }) ("in pass1, before assignments ", { foo => { bar => "nice" } }) ( "in pass1 after assignments ", { fizz => "fazz", foo => { bar => "KaPow" } }, ) ("after pass1: ", { foo => { bar => "KaPow" } })
    Bottom line: references be tricky. (Update: See perlref, perlreftut, maybe also perldsc.)


    Give a man a fish:  <%-{-{-{-<

      And for more information about the difference between shallow and deep copy, have a look at hash ref mind blow. It will eventually bite you one day.
Re: How to preserve values outside a sub
by stevieb (Canon) on Oct 25, 2017 at 15:53 UTC
    "Am I missing something obvious?"

    Yep :) A reference is a reference is a reference no matter how many times you re-assign it to something else, and always points to the same base item.

    You need to "copy" the reference in order to break the link:

    my $params = { %{ $self->{params} } };

    What that does is it dereferences the incoming param into an anonymous hashref (the outer braces), and that new "copied" reference is then assigned to $params.

Re: How to preserve values outside a sub
by RMGir (Prior) on Oct 25, 2017 at 15:52 UTC
    That's the way references work - what you're passing to your 'pass1' function is a pointer to the hash '$hashref' points to, not a copy of it, so local changes get applied to that global copy. If you want to pass a local copy, specify that:
    pass1({ params => {%$hashref} }); # {%$hashref} makes a (shallow) cop +y of the hash '$hashref' # points to and creates a new ref t +o it.
    If you do that, then you get:
    before pass1: 1 Inside pass1... 2 3 after pass1: 1
    which is what I think you expect.

    Added (shallow) note, AnomalousMonk is correct in his reply below!


    Mike
Re: How to preserve values outside a sub
by stevieb (Canon) on Oct 25, 2017 at 16:05 UTC

    Also, note that $self is kind of reserved (although not enforced) for a blessed object. The way you're assigning $self is and will be very confusing to people familiar with OO programming in Perl. If you are not using OO, don't use $self as the variable name. Here's an example using $self in proper context:

    use warnings; use strict; package Blah; { sub new { my ($class, $params) = @_; my $self = bless $params, $class; return $self; } sub display { my ($self) = @_; # should be using getters, but I digress for (keys %{ $self->{params} }){ print "$_: $self->{params}{$_}\n"; } } } package main; { my $hashref = { num1 => 1, num2 => 2 }; my $obj = Blah->new({params => $hashref}); $obj->display; }
Re: How to preserve values outside a sub
by kcott (Archbishop) on Oct 26, 2017 at 06:00 UTC
    "This sub localises (using "my") ..."

    As you've seen in multiple replies, that's not performing the localisation you were expecting. You can, however, get that behaviour with local. Here's a quick example to demonstrate that:

    $ perl -E ' my $x = { a => 1 }; say $x->{a}; { local $x->{a} = 2; say $x->{a}; } say $x->{a}; ' 1 2 1

    In general, I wouldn't do that; instead choosing one of the methods already shown, involving dereferencing the original hashref and creating a new one. There may be occasions, where you are working with very large data structures, and need to perform this operation many times (perhaps in a loop), that you run into efficiency issues; in these types of cases, especially when you only need to modify a small number of values, using local may be appropriate.

    perlsub has a number of sections with information about local. In particular, see "Localization of elements of composite types" and "Localized deletion of elements of composite types".

    Because I didn't see it mentioned in the linked documentation, I'll just point out that also works with slices. Here's another quick example, extending the previous one, to show that:

    $ perl -E ' my $x = { a => 1, b => 2 }; say "@$x{qw{a b}}"; { local @$x{qw{a b}} = (3,4); say "@$x{qw{a b}}"; } say "@$x{qw{a b}}"; ' 1 2 3 4 1 2

    And to show that the elements in the slice remain localised individually:

    $ perl -E ' my $x = { a => 1, b => 2 }; say "@$x{qw{a b}}"; { local @$x{qw{a b}} = (3,4); say "@$x{qw{a b}}"; $x->{b} = "XYZ"; say "@$x{qw{a b}}"; } say "@$x{qw{a b}}"; ' 1 2 3 4 3 XYZ 1 2

    Update (code reformatting): Upon revisiting this post about 10 hours after I originally wrote it, I observed that the first one-liner was possibly not as clear as I might have hoped, and the subsequent extensions to it resulted in that failing clarity heading in the direction of opacity. I've split each one-liner into multiple lines and added indentation to improve readability: none of the actual code has changed. Accordingly, I've also removed the "Apologies in advance for the wrapping" clause.

    — Ken

Re: How to preserve values outside a sub
by Laurent_R (Canon) on Oct 25, 2017 at 15:58 UTC
    Your variable $params contains the same thing as $hashref, i.e. a reference to the anonymous hash referred to by $hashref. $params and $hashref are different variable, but they hold the same value, the address of the anonymous hash. Thus, modifying the data referred to by one means also modifying the data referred to by the other.
Re: How to preserve values outside a sub
by karlgoethebier (Abbot) on Oct 26, 2017 at 09:59 UTC
    "...preserve values..."

    Mmh, i'm not sure if this is helpful but a while ago i played around with this:

    package Foo { use strict; use warnings; sub new { my ( $class, $foo ) = @_; bless \$foo, $class; \$foo; } sub foo { ${ (shift) }; } 1; } package Bar { use strict; use warnings; use parent qw(Foo); my $modify = sub { uc shift }; # my $modify = sub { ... }; sub bar { # my ( $self, $foo ) = @_; $modify->( shift->SUPER::foo() ); } 1; } #!/usr/bin/env perl use strict; use warnings; use feature qw(say); use Bar; my $object = Bar->new(q(lorem ipsum kizuaheli)); say for ( $object->foo(), $object->bar() ); __END__

    OK, it's a scalar but it could hash as well. The original is always available.

    I hope i didn't miss the point.

    Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: How to preserve values outside a sub
by Anonymous Monk on Oct 25, 2017 at 16:03 UTC

    Thank you so much guys :)))

    A mystery to me because I kept thinking by localising the incoming variable in pass1, the original hashref would be intact but when it does change, I was totally lost.

A reply falls below the community's threshold of quality. You may see it by logging in.