简体   繁体   中英

Inconsistent (silly?) data access in Perl 5 (also confusing me regarding use of sigils)

This question is about asking for some explanation of what's going on in the Perl system for I don't implicitly see the point though I'm coding for more than 25 years now. So here comes the story ...

On trying to work with Cyrus::IMAP::Admin instances in Perl5 I've tried to get and print a list of quotas resulting in a somewhat strangely structured data returned.

my %quotas = $client->listquota(@list[0]);

if ( $client->error ) {
    printf STDERR "Error: " . $client->error . "\n";
    exit 1;
}

print "root: " . $list[0] . "\n";

foreach my $quota ( keys %quotas ) {
    print( $quota, " ", $quotas{$quota}[0], "/", $quotas{$quota}[1], " KiB\n" );
}

This code is actually working as desired by printing out stuff like

root: user.myuser
STORAGE: 123/4567 KiB

This code was taken from Cyrus::IMAP::Shell reading similar to this:

my %quota = $$cyrref->listquota(@nargv);
foreach my $quota (keys %quota) {
    $lfh->[1]->print(" ", $quota, " ", $quota{$quota}[0], "/", $quota{$quota}[1]);
    if ($quota{$quota}[1]) {
        $lfh->[1]->print(" (", $quota{$quota}[0] * 100 / $quota{$quota}[1], "%)");
    }
}

This code looks somewhat silly to me for using $quota{$quota}[0] . In my case I renamed variables a bit for rejecting that mixed use of differently typed but equivalently named variables.

Prior to taking the code from Cyrus::IMAP::Admin I tried to understand its specification and to process the result by code written myself. It looked like this:

my %quotas = $client->listquota(@list[0]);

if ( $client->error ) {
    printf STDERR "Error: " . $client->error . "\n";
    exit 1;
}

print "root: " . $list[0] . "\n";

foreach my $quota ( keys %quotas ) {
    my @sizes = @quotas{$quota};
    print( $quota, " ", $sizes[0], "/", $sizes[1], "\n" );
}

However, this code didn't work and I didn't find any plausible explanation myself. My understanding here is that transferring this last code example to the initially posted form would require to have source of assignment in line 11 substituted into usages in line 12 and change the sigil of quotas from @ to $ for I'm trying to get a scalar result finally . This last code was printing an array reference before slash and nothing after it. So I had to fix my code like this to get it working:

my %quotas = $client->listquota(@list[0]);

if ( $client->error ) {
    printf STDERR "Error: " . $client->error . "\n";
    exit 1;
}

print "root: " . $list[0] . "\n";

foreach my $quota ( keys %quotas ) {
    my @sizes = @quotas{$quota};
    print( $quota, " ", $sizes[0][0], "/", $sizes[0][1], "\n" );
}

This additional dereferencing in line 12 is what I'm confused about now. Why is @sizes containing an array storing another array in its sole first element? For being confused I already tried alternative code in line 11 to no avail. These tests included

    my @sizes = $quotas{$quota};

(for its equivalence with original code posted above) and

    my $sizes = @quotas{$quota};

(for I don't know why). Switching sigils don't seem to change semantics of assignment here at all. But using this assignment seems to open different view on data structure contained in %quotas originally. What sigils are required to have @sizes matching content and structure of $quotas{$quota} as used in top-most code fragment?

I believe you want this on your line 11:

my @sizes = @{ $quotas{$quota} };

Also, recommend you start using Data::Dumper everywhere.

Eg

use Data::Dumper;

print 'Data structure of \\%quotas: ' . Dumper(\\%quotas) . qq(\\n);

That way you can be sure what structure you are dealing with.

$quotas{$quota} accesses a single scalar element in %quotas . @quotas{$quota} is a hash slice which selects a list of one element from the hash.

  • $collection[...] or $collection{...} : Single scalar element

     my @array = (1, 2, 3, 4); my $elem = $array[1]; #=> 2 
  • @collection[...] or @collection{...} : list of elements

     my @array = (1, 2, 3, 4); my @slice = @array[1, 3]; #=> (2, 4) 
  • %collection[...] or %collection{...} : key-value list, not yet available except in “blead”.

     my @array = (1, 2, 3, 4); my %kvs = %array[1, 3]; #=> (1 => 2, 3 => 4) 

Using another sigil when referencing an element in a collection does not dereference the element!

Now what does or single-element list @quotas{$quota} contain? It is an array reference [...] .

  • When you assign this to a scalar, the list is evaluated in scalar context and gives the last element:

     my $sizes = @quotas{$quota}; my $sizes = ([...]); my $sizes = [...]; [...] 

    Accessing an element in that array reference then looks like $sizes->[0] .

  • When you assign this to an array, you create an array that holds as a single element this array reference:

     my @sizes = @quotas{$quota}; my @sizes = ([...]); ([...]) 

    Accessing an element in that array reference then looks like $sizes[0][0] , because you first have to get at that array reference inside the array @sizes .

… and incidentally the same happens here when you do $quotas{$quota} , but for slightly different reasons.

If you want to dereference an array reference to an array, use curly braces:

  • my @foo = @{$array_refernce} which copies the contents
  • my $elem = ${$array_reference}[0] which accesses an element, the same as $array_reference->[0] .

So you could do

my @sizes = @{ $quotas{$quota} };

to dereference the array and copy it to an array variable. You can then access an element like $sizes[0] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM