views:

140

answers:

2

Given this code:

#!/usr/bin/perl -w

use strict;
use warnings;

sub foo {
    return wantarray ? () : "value1";
}

my $hash = {
    key1 => foo(),
    key2 => 'value2'
};

use Data::Dumper;
print Dumper($hash);

I get the following output:

$VAR1 = {
  'key1' => 'key2',
  'value2' => undef
};

When I would expect:

$VAR1 = {
  'key1' => 'value1',
  'key2' => 'value2'
};

I understand that a hash is kind-of an even-sized array (as evidenced by the "Odd number of elements in hash assignment" warning I'm getting) but a hash element can only be a scalar, why would the compiler be giving it array context?

I found this using the param function of the CGI module when assigning directly to a hash. The foo() function above was a call to CGI::param('mistyped_url_param') which was returning an empty array, destroying (rotating?) the hash structure.

+7  A: 

The fat comma isn't a special hash assignment operator. It just a piece of syntactic sugar that means "auto-quote the previous thing"

So:

my $hash = {
    key1 => foo(),
    key2 => 'value2'
};

Means:

my $hash = {
    'key1', foo(), 'key2', 'value2'
};

… which is a list and, as willert says:

every expression in a list is evaluated in list context. You can get around this by calling scalar foo()

David Dorward
While that's true, why is `foo()` evaluated in array context there, when it seems to be clearly an *element* of a list?
Greg Hewgill
@Greg: because there is no such thing as 'array context'. Void, scalar and list is (mostly) all you get from pure-perl. And list context is equivalent to what you seem to think of as 'array context'
willert
Unless you reference the array, it is evaluated to its contents. So for 'my @a1 = (1, 2); my @a2 = (@a1, 3, 5, 8);', the second statement is evaluated as '((1, 2), 3, 5, 8)' and the length of @a2 is 5, not 4. An empty list is effectively ignored.
Quick Joe Smith
@willert: With a Perl keyword like `wantarray`, one might assume that the names "list context" and "array context" are interchangeable. At least I don't see any possible confusion. Still, that doesn't answer my question of why `foo()` is called in a *list* context there.
Greg Hewgill
Every expression in a list is evaluated in list context. You can get around this by calling "scalar foo()", though.
willert
@Greg for `%hash = ( one => 1, two => 2 )` to work, it has to be a list context.
Brad Gilbert
@Greg Hewgill: Loosely speaking, Perl interprets the call to `foo()` as a slice, not an element. It's perfectly valid for `foo()` to return a list of values which the hash initialization will use as key/value pairs.
Michael Carman
@Michael: Thanks, I think that answers my question.
Greg Hewgill
`wantarray` is an unfortunate legacy name.
brian d foy
Good discussion... Although to me it looks like a leaky implementation in Perl. The fat comma is aware of context - it stringifies the bare word on the left-hand side (even when it's a constant). I guess it'd be nice if it applied the scalar context to the right-hand side as well, since not doing so breaks the hash.
C4H5As
+2  A: 

An anonymous hash constructor supplies list context to the things inside it because it expects a list of keys and values. It's that way because that's the way it is. We don't have a way to represent a Perl hash in code, so you have to use a list where we alternate keys and values. The => notation helps us visually, but performs no magic that helps Perl figure out hashy sorts of things.

Current context propogates to subroutine calls, etc just like it does in any other situation.

This allows you to build hashes with list operations:

 my $hash = { 
      a => 'A',  
      map( uc, 'd' .. 'f' ),
      return_list( @args ), 
      z => 'Z' 
      };

If you need something to be in scalar context, just say so using scalar:

 my $hash = { 
      a => 'A',  
      map( uc, 'd' .. 'f' ),
      'g' => scalar return_item( @args ), 
      z => 'Z' 
      };
brian d foy