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

I was recently helping someone debug code in which the pass a hash to a subroutine by reference. They had done something like:
%foo = ( a => 1, b => 2, c => 3 }; sub mysub { my %x = shift; ... }
It was clear to me that the assignment a scalar reference directly to a hash variable would result in a hash containing a single pair, with key the actual reference value, and value undef. I usually shift onto a scalar variable and then use a dereference operator (->) in the sub when I pass hashes around. The person I was helping wanted to keep the hash variable %x, however. So I tried a couple things and eventually got this to work:
my %foo = ( a => 1, b => 2, c => 3 ); mysub(\%foo); sub mysub { my %x = %{scalar shift}; foreach ( sort keys %x ) { print "$_ -> $x{$_}\n"; } }
That %x = %{scalar shift} is a pretty nifty little incantation. I tried to figure out why it works. Based on my experience, clearly this works:
my %foo = ( a => 1, b => 2, c => 3 ); mysub(\%foo); sub mysub { my $x = shift; my %x = %{$x}; foreach ( sort keys %x ) { print "$_ -> $x{$_}\n"; } }
However, when I tried to simplify the code above, I was started that this does NOT work:
my %foo = ( a => 1, b => 2, c => 3 ); mysub(\%foo); sub mysub { my %x = %{shift}; foreach ( sort keys %x ) { print "$_ -> $x{$_}\n"; } }
The above code prints out nothing. What is magic about adding that "scalar"???

Replies are listed 'Best First'.
Re: Pass By Reference Inner Workings - Magic scalar operator
by Zaxo (Archbishop) on May 01, 2005 at 07:39 UTC

    Your "shift" is being mistaken for a bareword hash key symbolic reference (thanks, davido for the correction). If you say,

    my %x = %{+shift}; # or my %x = %{shift()};
    that will suffice. Anything to break up the bareword evaluation to an literal key identifier. Using strict and warnings will help spot that kind of thing.

    After Compline,
    Zaxo

Re: Pass By Reference Inner Workings - Magic scalar operator
by davido (Cardinal) on May 01, 2005 at 07:43 UTC

    scalar is working for you, not because of its primary effect of forcing scalar context, but because of one of its secondary effects; of making your expression look like an expression, rather than just a bareword.

    For example, you could also have said "my %x = %{+shift}". And that too would have worked.

    The point is that %{shift} looks like a hash named "shift". %{+shift} looks like a dereferencing of the return value of the function named shift. And %{scalar shift} is recognized also by the compiler to be a set of function calls instead of just a literal variable name.


    Dave

      davido, I'm surprised and ashamed for you. You should have told him about shift() before or instead of +shift. The former looks normal and has identical functionality while the latter is just an abuse of unary +.

      ... = %{shift()}

        :) Surprise and shame huh?

        Is it abuse, or acceptible use? Either way, the Perl world will survive my sin. ;) It's not all that unusual of a sight anyway, though to your point it is not as explicit as shift().

        Thanks for the gentle reminder that we shouldn't get too fancy.


        Dave

      Am I correct that by saying something like
      sub mysub { my %x = %{+shift}; }
      I'll actually be copying all of the hash entries into a local variable... So if the hash passed into the subroutine is very large, it could be a performance hit? The paradigm I usually use is to shift into a scalar and then use dereference operator to access elements. This may be why... unless you have really good reasons to do it, should one stay away from the paradigm in the code segment above???

        First thing's first; diotalevi is right, my %x = %{shift()}; is by far the preferred method. I pulled out the unary + as a prefix that served a similar purpose to your use of scalar in this situation. But it's ultimately a bad example, and shift() is the proper way to do things. Treat a function like a function, (ie, use parens) rather than using the unary plus +.

        Now to answer your question: Yes, my %x = %{shift()}; makes a copy of the contents of the hash to a lexical variable. This does cost performance and memory. But there are times when you need a copy, and other times where it's fine to just work with the reference to the original hash, held in $_[0].


        Dave

Re: Pass By Reference Inner Workings - Magic scalar operator
by Roy Johnson (Monsignor) on May 01, 2005 at 15:32 UTC
    If you wanted to use a hash variable as an alias to the original hash, it would go like this:
    use strict; use warnings; my %foo = ( a => 1, b => 2, c => 3 ); mysub(\%foo); sub mysub { our (%x); local *x = shift; foreach ( sort keys %x ) { print "$_ -> $x{$_}\n"; } }

    Caution: Contents may have been coded under pressure.
Re: Pass By Reference Inner Workings - Magic scalar operator (@_ FCS)
by tye (Sage) on May 02, 2005 at 03:26 UTC

    This entire thread never even mentioned @_ once! It mentioned $_[0] exactly once but never used it in code.

    So I'll be the first to mention:

    my %h= %{ shift @_ };

    Leaving the target of operations implied is fine for quick hacks, one-liners, and golf. But I usually stress writing code that is easy to maintain so I usually avoid such implied targets. And this means I don't get bit by shift or pop being treated as a string.

    It also means that you can easily find where I deal with function arguments. As opposed to this code from a core module. Quick, what arguments does it take?

    At least the suggested %{shift()} makes the code somewhat clearer. But both %{+shift} and %{(shift)} are, if anything, somewhat obfuscatory, which isn't an awful thing, but since there are such clearer alternatives I don't consider them good options.

    - tye        

Re: Pass By Reference Inner Workings - Magic scalar operator
by TedPride (Priest) on May 01, 2005 at 17:15 UTC
    I must be missing something here...
    use strict; use warnings; my %hash = (1,'a',2,'b',3,'c'); print $hash{2}; mysub(\%hash); print $hash{2}; sub mysub { my $p = shift; print $p->{2}; $p->{2} = 'd'; }
    or if not actually modifying the contents of the hash inside the sub...
    use strict; use warnings; my %hash = (1,'a',2,'b',3,'c'); mysub(\%hash); sub mysub { my %h = %{shift()}; print $h{2}; }
    You pass by reference in the second case too, so that if you wanted to pass multiple arguments after the hash, you wouldn't have problems determining where the hash ended and the next argument began - which you would if you passed the contents of the hash. I'm sure there are ways to get around this problem using sub definitions instead, but that way of doing things would lack elegance imho.