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

Hello! I often find myself needing to input and return multiple references from a subroutine and end up writing code that takes the following form:

my %x = ("a" => "red"); my %y = ("b" => "green"); my %z = ("c" => "black"); my ($ref1, $ref2, $ref3) = &modfifyHash(\%x, \%y, \%z); %x = %{ $ref1 }; %y = %{ $ref2 }; %z = %{ $ref3 }; sub modfifyHash { my ($ref1, $ref2, $ref3) = @_; my %x = %{ $ref1 }; my %y = %{ $ref2 }; my %z = %{ $ref3 }; $x{ "a" } = "circle"; $y{ "b" } = "square"; $z{ "c" } = "rectangle"; return(\%x, \%y, \%z) }

The, 1) create a variable to hold the reference, 2) dereference each reference, steps takes up considerable amount of space and feels like it just clutters the whole code a lot (especially when I end up doing this many times throughout a script and/or am inputing or returning many referenced variable). So I am looking for a more compact and elegant solution.... I would like to write something like:

my %x = ("a" => "red"); my %y = ("b" => "green"); my %z = ("c" => "black"); (%x, %y, %z) = %{ &modfifyHash(\%x, \%y, \%z) }; sub modfifyHash { my (%x, %y, %z) = %{ @_ }; $x{ "a" } = "circle"; $y{ "b" } = "square"; $z{ "c" } = "rectangle"; return(\%x, \%y, \%z) }

...but this does not produce the desired results, i.e. each input/returned hash in an individual variable. I also thought I might be able to use map in some clever way like:

my %x = ("a" => "red"); my %y = ("b" => "green"); my %z = ("c" => "black"); (%x, %y, %z) = map %{ $_ }, &modfifyHash(\%x, \%y, \%z); sub modfifyHash { my (%x, %y, %z) = map %{ $_ }, @_; $x{ "a" } = "circle"; $y{ "b" } = "square"; $z{ "c" } = "rectangle"; return(\%x, \%y, \%z) }

...but, again, this is not working as I would like. Could anyone share how they typically handle this in a better way?

Replies are listed 'Best First'.
Re: Elegantly dereferencing multiple references
by LanX (Saint) on May 13, 2016 at 11:55 UTC
    returning refs can be done as

     return \(%x, %y, %z)

    but if your goal is to modify these hashes, why don't you operate on the references without returning them?

    use strict; use warnings; use Data::Dump; my %x = ( a => "red"); my %y = ( b => "green"); my %z = ( c => "black"); sub modfifyHash { my ($x, $y, $z) = @_; $x->{a} = "circle"; $y->{b} = "square"; $z->{c} = "rectangle"; return; } dd \(%x, %y, %z); modfifyHash( \(%x, %y, %z) ); dd \(%x, %y, %z);

    now tested!

    ({ a => "red" }, { b => "green" }, { c => "black" }) ({ a => "circle" }, { b => "square" }, { c => "rectangle" })

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

    update

    added tested version

Re: Elegantly dereferencing multiple references
by Eily (Monsignor) on May 13, 2016 at 11:56 UTC

    Is there a reason why you don't work directly on the hashrefs ?

    use Data::Dumper; my %x = ("a" => "red"); my %y = ("b" => "green"); modifyHash( \(%x, %y) ); sub modifyHash { my ($x, $y) = @_; $x->{a} = "circle"; $x->{b} = "square"; } print Dumper ( \(%x, %y) );
    Maybe you can avoid having separate hashes in the first place :
    use Data::Dumper; my %coords = ( x => { a => "Red" }, y => { b => "Green" } ); modifyHash(\%coords); sub modifyHash { my ($c,) = @_; $c->{x}{a} = "circle"; $c->{y}{b} = "square"; } print Dumper \%coords;

Re: Elegantly dereferencing multiple references
by haukex (Archbishop) on May 13, 2016 at 12:20 UTC

    Hi Anonymous,

    Others have already pointed out you can just edit the hashes through the references directly, and that seems like the way to go in your case. Just to explain why your second two examples don't work: Arrays or hashes on the left hand side of a my (...) = ... assignment only make sense as the last item being assigned to, because they slurp up all the remaining values from the right hand side.

    That means when you write my (%x, %y, %z) = ..., then the hash %x will slurp up all the values assigned to it:

    $ perl -wMstrict -MData::Dumper -le 'my (%x,%y) = (1,2,3,4); print Dum +per(\%x,\%y)' $VAR1 = { '1' => 2, '3' => 4 }; $VAR2 = {};

    Also, be aware that my %copy = %hash; only makes a shallow copy, so if %hash contains values which are references to other data, then those will reference the same data structures - which may not be what you want. If you want deep copies, Storable's dclone or Clone may be able to help you there.

    And just for completeness, here's one ("elegant"?) way to make the shallow copies:

    use warnings; use strict; use Data::Dump 'pp'; my %x = ("a" => "red"); my %y = ("b" => "green"); my %z = ("c" => "black"); pp \%x, \%y, \%z; my ($r1, $r2, $r3) = modfifyHash(\%x, \%y, \%z); pp $r1, $r2, $r3; pp \%x, \%y, \%z; # these remain unmodified sub modfifyHash { #my ($x, $y, $z) = @_; # this would modify the original hashes my ($x, $y, $z) = map { { %{ $_ } } } @_; # ^ ^ ^ # | | for each argument # | dereference as hash # make new hash reference (shallow copy!) $x->{a} = "circle"; $y->{b} = "square"; $z->{c} = "rectangle"; return $x, $y, $z; } __END__ ({ a => "red" }, { b => "green" }, { c => "black" }) ({ a => "circle" }, { b => "square" }, { c => "rectangle" }) ({ a => "red" }, { b => "green" }, { c => "black" })

    Although I would second Eily's suggestion that it might be a good idea to combine the hashes into one.

    Hope this helps,
    -- Hauke D

Re: Elegantly dereferencing multiple references
by Yary (Pilgrim) on May 13, 2016 at 14:47 UTC
    I agree with simply operating on the refs in-place. On the other hand, functional style is is good, and if you don't want to modify the input hashes, you can still have an elegant call/return by using refs throughout the code:
    my $x = {"a" => "red"}; my $y = {"b" => "green"}; my $z = {"c" => "black"}; my ($new_x, $new_y, $new_z) = &modfifyHash($x, $y, $z); # original $x is unchanged, $new_x has new value print "\$x->{a} = $x->{a}\t\$new_x->{a} = $new_x->{a}\n"; sub modfifyHash { my ($x, $y, $z) = @_; # If we don't want to modify the originals, # then make a shallow copy for each $_ = { %$_ } for $x,$y,$z; $x->{ "a" } = "circle"; $y->{ "b" } = "square"; $z->{ "c" } = "rectangle"; return($x, $y, $z) }
Re: Elegantly dereferencing multiple references
by polettix (Vicar) on May 19, 2016 at 14:52 UTC
    Others pointed out about using the reference directly with the -> dereferencing syntax. That's what I would do.

    As of perl 5.22 you can also use refaliasing. In your specific example - where you want to modify the input hashes - you might do as follows:

    #!/usr/bin/env perl use strict; use warnings; use Data::Dumper; use 5.024; use feature 'refaliasing'; no warnings 'experimental::refaliasing'; my %x = ("a" => "red"); my %y = ("b" => "green"); my %z = ("c" => "black"); modfifyHash(\(%x, %y, %z)); say Dumper \(%x, %y, %z); sub modfifyHash { \my (%x, %y, %z) = @_; $x{ "a" } = "circle"; $y{ "b" } = "square"; $z{ "c" } = "rectangle"; } __END__ $VAR1 = { 'a' => 'circle' }; $VAR2 = { 'b' => 'square' }; $VAR3 = { 'c' => 'rectangle' };
    This is probably the closest approximation to pass-by-reference without resorting to prototypes (it only costs you two backslashes in this case).

    perl -ple'$_=reverse' <<<ti.xittelop@oivalf

    Io ho capito... ma tu che hai detto?
Re: Elegantly dereferencing multiple references
by LanX (Saint) on May 13, 2016 at 12:16 UTC
    FWIW it's possible in one statement but hardly "elegant"
    use strict; use warnings; use Data::Dump; my %x = ( a => "red"); my %y = ( b => "green"); my %z = ( c => "black"); sub assign { my (%x, %y, %z) = ( %{$_[0]}, %{$_[1]}, %{$_[2]} ); dd \(%x, %y, %z); }

    I'll post later a more elegant but significantly slower solution

    UPDATE:
    I was wrong, never mind. Thanks to Eily++

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

      I was wondering what trick you used to prevent the flattened list to go into the first hash only. Looks like there's none:

      $VAR1 = { 'c' => 'black', 'a' => 'red', 'b' => 'green' }; $VAR2 = {}; $VAR3 = {};

        oops you are right, I had various outputs from Data::Dump and only saw what I expected to see... :-/

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

      as compensation an even less elegant (read uglier) attempt for a one liner:

      sub assign { %$_ = %{shift @_} for \ (my (%a, %b, %c)); dd ["assign", \(%a, %b, %c) ]; } assign( \(%x, %y, %z));

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!