Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

hash slice ? No thanks, I'm about to return...

by leriksen (Curate)
on Feb 19, 2005 at 10:30 UTC ( [id://432691]=perlmeditation: print w/replies, xml ) Need Help??

Got bit by this - trying to be a bit too clever.

I had a subroutine that was returning a hash - I refactored it so that all the assignments to the hash in the sub happened in a nice clear hash slice operation - however, I found the caller wasn't getting back the right result.

my %run_factors = $this_week->get_run_factors(); ... package Week; ... sub get_run_factots { my ($self) = @_; my %run_factors; @run_factors{$self->active_runs()} = $self->factors(); }
Problem was I was returning the result of the hash slice assignment - which is actually the list on the right hand side of the assignment, not the hash itself. A more minimal example is ...

#!/usr/bin/perl -w use strict; my @keys = qw(a b c d); my @values = (1, 2, 3, 4); my %result = build_hash(); use Data::Dumper; print STDERR Dumper(\%result); sub build_hash { my %return; @return{@keys} = @values; }
Gives

$VAR1 = { '1' => 2, '3' => 4 };
So beware - dont make the hash assignment be the last thing you do before returning home...

...it is better to be approximately right than precisely wrong. - Warren Buffet

Replies are listed 'Best First'.
Re: hash slice ? No thanks, I'm about to return...
by Aristotle (Chancellor) on Feb 19, 2005 at 13:19 UTC

    Your intent is not clear. What do you mean by “returning a hash”? You can't do that, per se. Did you mean to return a hash ref or a list of key/value pairs that can be assigned to a hash?

    In either case the hash slice is a red herring. Quoth perlop under Assignment Operators:

    […] a list assignment in list context produces the list of lvalues assigned to, […]

    which matches the exact behaviour you described.

    If you want to return a hash ref you're going about this wrong. That code would be

    sub build_hash { my %return; @return{@keys} = @values; \%return; }

    Returning a list of key/value pairs requires more list-fu:

    sub build_hash { ( @keys, @_ )[ map { $_, $_ + @keys } 0 .. $#keys ]; }

    Makeshifts last the longest.

      My intent was to return a list of key-value pairs, as in the code sample - and I'm not sure I agree with your doc-quote

      a list assignment in list context produces the list of lvalues assigned to,

      Isnt the list assigned to the hash(or list of key/value pairs) %return ?
      Isnt the list assigned to on the left hand side of the '=' ?
      In the second code samples case that would be (a,1,b,2,c,3,d,4), yes?

      The list assigned _from_ is what I'm seeing returned..

      Apart from that the list-fu is a neat trick, but perhaps the explicit return is more appropriate, as discussed later in this thread, or a better understanding of when to use return...

      ...it is better to be approximately right than precisely wrong. - Warren Buffet

        You misunderstand.

        No, the list assigned to is not %return. You are taking a hash slice. The hash as a whole is nowhere to be seen in this assignment. I get the feeling that you haven't understood exactly what taking a slice does.

        Yes, the list assigned to is on the left hand of the assignment operator.

        I'm not sure which second code sample you are talking about.

        You are not seeing the list assigned from. You are seeing what the list you assigned to evaluates to, which in this case is 1, 2, 3, 4. The distinction is subtle and may be confusing but you are indeed getting the list assigned to:

        #!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %return; my @value = qw( foo bar baz quux ); $_ = uc $_ for @return{ qw( a b c d ) } = @value; print Dumper( \%return ), Dumper( \@value ); =begin output $VAR1 = { 'c' => 'BAZ', 'a' => 'FOO', 'b' => 'BAR', 'd' => 'QUUX' }; $VAR1 = [ 'foo', 'bar', 'baz', 'quux' ];

        As you can see, the $_ = uc $_ assignment changed the values in the hash. If you were getting the list assigned from, it would have changed the values in @value.

        Makeshifts last the longest.

Re: hash slice ? No thanks, I'm about to return...
by Ovid (Cardinal) on Feb 19, 2005 at 16:06 UTC

    From perldoc perlsub: The return value of a subroutine is the value of the last expression evaluated by that sub...

    The value of an assignment operation in list context is generally the value(s) being assigned. Thus, when you're assigning a list to something, that's the value of the assignment and therefore that's the return value of the subroutine.

    Cheers,
    Ovid

    New address of my CGI Course.

      Thanx Ovid, now I guess I'll have to research what circumstances are not covered by the "generally"
      ....starts shuffling through rudely stacked piles of perldoc covering his desk...

      ...it is better to be approximately right than precisely wrong. - Warren Buffet

Re: hash slice ? No thanks, I'm about to return...
by thor (Priest) on Feb 19, 2005 at 16:19 UTC
    ...which is why I always do an explicit return. Some might consider it baby Perl to do so, but I don't rely on the value of the last expression in a subroutine for the return value of a sub. In your case, if you put a "return %return" at the end of your sub, everything's golden.

    thor

    Feel the white light, the light within
    Be your own disciple, fan the sparks of will
    For all of us waiting, your kingdom will come

      Or just a "%return" - return is optional. Either way, make the return value explicit, rather than relying on a complex operation to do it, whether you make the fact you're returning explicit or not.

      (I did some benchmarks a while back which surprised me to find out that "return %hash" was actually slower than "%hash" and falling out the bottom of the function...)

        You surprised me with this, but:
        Rate return no ret return 4682554/s -- -30% no ret 6642457/s 42% --
        Is what i got from:
        use Benchmark qw( cmpthese ); my %hash = (1, 2, 4, 6, 43, 32, 5, 6, 6, 43, 3, 54, 5, 1); cmpthese(0, { 'return' => sub { return \%hash }, 'no ret' => sub { \%hash }, });

        Depends. I often implicitly return the result of an operation that would obviously be useless in void context, as in

        sub as_string { my $self = shift; join '-', $self->date; }

        or the like.

        Makeshifts last the longest.

      We actually have it as a coding standard. The explicit return is required. It's obvious to some, but the time spent by a beginner or intermeddiate programmer thinking about exactly what is being returned can offset the shortcut. We have quite a lot of perl code, so we often have beginner level people looking at stuff and implicit return values really slow them down.

      The performance penalty is interesting. I wasn't aware of that. Might add up if you had a sub that is called many times in your program.

        so we often have beginner level people looking at stuff and implicit return values really slow them down
        It would be faster to take the 15 minutes to teach each of them about Perl's very simple return policy ("last expression evaluated is always the return value") than to require every coder in the world to make up for their lack of training.

        Even if you could establish policy for newly written code at your organization, these coders will always be looking at code from the CPAN or from consultants that clearly are not going to put in all of your handholding.

        I don't want to suggest that coding standards are categorically silly, but don't try to make Perl look like another language in the standard. Lack of the "return" keyword is both idiomatic and commonplace.

        It's a bit like demanding that every sharp corner in the workplace be covered with Nerf foam, just because there are some people who walk around with their eyes shut. No, it'd be simpler to teach them to watch where they are going.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

      I tend to agree with you, except for certain situations. I will get rid of the return when my function is a one-liner, along the lines of:
      sub foo { (+shift)->foo_bar( 'foo', @_ ) } sub bar { (+shift)->foo_bar( 'foo', @_ ) } sub foo_bar { my $self = shift; my ($type, @args) = @_; # Do stuff here } sub unique { my %x; @x{@_} = @_; values %x } sub stringify { join '-', $_[0]->date }

      If my function gets to more than 80 characters, including the subroutine name, and/or it isn't a drop-in replacement for where it's called, then I use more than one line, don't directly access @_, and use an explicit return.

      (I access @_ for aliasing-type stuff as well, but I'll do it here for brevity.)

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://432691]
Approved by grinder
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (2)
As of 2024-04-26 06:02 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found