Search code examples
perlsigils

Why I can use @list to call an array, but can't use %dict to call a hash in perl?


Today I start my perl journey, and now I'm exploring the data type.

My code looks like:

@list=(1,2,3,4,5);
%dict=(1,2,3,4,5);

print "$list[0]\n";         # using [ ] to wrap index
print "$dict{1}\n";         # using { } to wrap key

print "@list[2]\n";
print "%dict{2}\n";

it seems $ + var_name works for both array and hash, but @ + var_name can be used to call an array, meanwhile % + var_name can't be used to call a hash.

Why?


Solution

  • @list[2] works because it is a slice of a list.

    In Perl 5, a sigil indicates--in a non-technical sense--the context of your expression. Except from some of the non-standard behavior that slices have in a scalar context, the basic thought is that the sigil represents what you want to get out of the expression.

    If you want a scalar out of a hash, it's $hash{key}. If you want a scalar out of an array, it's $array[0]. However, Perl allows you to get slices of the aggregates. And that allows you to retrieve more than one value in a compact expression. Slices take a list of indexes. So,

    @list = @hash{ qw<key1 key2> };
    

    gives you a list of items from the hash. And,

    @list2 = @list[0..3];
    

    gives you the first four items from the array. --> For your case, @list[2] still has a "list" of indexes, it's just that list is the special case of a "list of one".

    As scalar and list contexts were rather well defined, and there was no "hash context", it stayed pretty stable at $ for scalar and @ for "lists" and until recently, Perl did not support addressing any variable with %. So neither %hash{@keys} nor %hash{key} had meaning. Now, however, you can dump out pairs of indexes with values by putting the % sigil on the front.

    my %hash = qw<a 1 b 2>;
    my @list = %hash{ qw<a b> }; # yields ( 'a', 1, 'b', 2 )
    my @l2   = %list[0..2];      # yields ( 0, 'a', 1, '1', 2, 'b' )
    

    So, I guess, if you have an older version of Perl, you can't, but if you have 5.20, you can.


    But for a completist's sake, slices have a non-intuitive way that they work in a scalar context. Because the standard behavior of putting a list into a scalar context is to count the list, if a slice worked with that behavior:

    ( $item = @hash{ @keys } ) == scalar @keys;
    

    Which would make the expression:

    $item = @hash{ @keys };
    

    no more valuable than:

    scalar @keys;
    

    So, Perl seems to treat it like the expression:

    $s = ( $hash{$keys[0]}, $hash{$keys[1]}, ... , $hash{$keys[$#keys]} );
    

    And when a comma-delimited list is evaluated in a scalar context, it assigns the last expression. So it really ends up that

    $item = @hash{ @keys }; 
    

    is no more valuable than:

    $item = $hash{ $keys[-1] };
    

    But it makes writing something like this:

    $item = $hash{ source1(), source2(), @array3, $banana, ( map { "$_" } source4()};
    

    slightly easier than writing:

    $item = $hash{ [source1(), source2(), @array3, $banana, ( map { "$_" } source4()]->[-1] }
    

    But only slightly.