scalar and hash slices -- is it supposed to work this way?

R

Richard Harman

I think I found a bug in perl when dealing with hash slices. For some
reason, scalar is interpreting the returned list from a hash slice as a
'scalar comma expression' and is returning the final element (perldoc -f
scalar, 3rd paragraph). Is this the way it's supposed to work? (If this
is not the correct place to ask, where do I go?)

# data
my %hash = ( first=>["Studly","Caps"], second=>["Make","Things","Readable"]);
my $hashref = \%hash;

# slicing the hashref for values (the arrays)
my @array_refs = @$hashref{qw(first second)};

# print out the memory addresses
printf "Contents of hash:\n";
foreach my $key (keys %hash) { print "$key => $hash{$key}\n"; };

printf("Scalar array_refs: %s\n",scalar @array_refs);

# now why doesn't this return 2? A slice is just a list, right?
printf("Scalar hashref slice WRONG: %s\n",scalar @$hashref{qw(first second)});

# but this works (forced list interpolation)
printf("Scalar hashref forced list interpolation RIGHT: %s\n",
scalar @{[@$hashref{qw(first second)}]});
 
E

Eric Bohlman

I think I found a bug in perl when dealing with hash slices. For some

May people have thought they found bugs in perl. Few of them really
have.
reason, scalar is interpreting the returned list from a hash slice as
a 'scalar comma expression' and is returning the final element
(perldoc -f scalar, 3rd paragraph). Is this the way it's supposed to
work? (If this is not the correct place to ask, where do I go?)

Yes, it's the way it's supposed to work. The entry "What is the
difference between a list and an array?" in perlfaq4 is a good place to
start.
# data
my %hash = ( first=>["Studly","Caps"],
second=>["Make","Things","Readable"]); my $hashref = \%hash;

# slicing the hashref for values (the arrays)
my @array_refs = @$hashref{qw(first second)};

# print out the memory addresses
printf "Contents of hash:\n";
foreach my $key (keys %hash) { print "$key => $hash{$key}\n"; };

printf("Scalar array_refs: %s\n",scalar @array_refs);

@array_refs is an array, and so when put in scalar context evaluates to
the number of elements. That's a property of *arrays* in particular.
# now why doesn't this return 2? A slice is just a list, right?
printf("Scalar hashref slice WRONG: %s\n",scalar @$hashref{qw(first
second)});

Your slice is indeed a list, but it's not an array, so it doesn't have
that particular (peculiar?) property.
# but this works (forced list interpolation)
printf("Scalar hashref forced list interpolation RIGHT: %s\n",
scalar @{[@$hashref{qw(first second)}]});

You've actually created an array here.
 
R

Richard Harman

May people have thought they found bugs in perl. Few of them really
have.

I suspected as much :)
Yes, it's the way it's supposed to work. The entry "What is the
difference between a list and an array?" in perlfaq4 is a good place to
start.

Wow, according to perlfaq4 I'm doing all sorts of bad things, like
($exits) = grep { m/pattern/ } @array;

I really should read perlfaqX more often. One would think reading the
various ORA books would glean such information. Although, what I was
trying to "efficiently" do has turned out to be inefficient, so I think I
get to chuck this thought process entirely :) Thanks for the pointer to
perlfaq4.


<snip>
 
P

Paul Lalli

Richard said:
I think I found a bug in perl

I should caution you that that statement can be, and perhaps has been,
seen as exceedingly arrogant. Do you really suspect that with the
multitude of people around the world using Perl, that *you* are the
first person to discover a bug?
when dealing with hash slices. For some
reason, scalar is interpreting the returned list from a hash slice as a
'scalar comma expression' and is returning the final element (perldoc -f
scalar, 3rd paragraph). Is this the way it's supposed to work? (If this
is not the correct place to ask, where do I go?)

I don't understand your question. Yes, a "list" in scalar context
returns the last element of that list. Where is the contradiction?
# data
my %hash = ( first=>["Studly","Caps"], second=>["Make","Things","Readable"]);
my $hashref = \%hash;

# slicing the hashref for values (the arrays)
my @array_refs = @$hashref{qw(first second)};

# print out the memory addresses
printf "Contents of hash:\n";
foreach my $key (keys %hash) { print "$key => $hash{$key}\n"; };

printf("Scalar array_refs: %s\n",scalar @array_refs);

# now why doesn't this return 2? A slice is just a list, right?
printf("Scalar hashref slice WRONG: %s\n",scalar @$hashref{qw(first second)});

Why *would* it return 2? A slice is indeed, a list. A list in scalar
context returns the last element of that list. An *array* in scalar
context, on the other hand, returns the size of that array.

perldoc -q "list and an array"
# but this works (forced list interpolation)
printf("Scalar hashref forced list interpolation RIGHT: %s\n",
scalar @{[@$hashref{qw(first second)}]});

Here you've taken the hash slice (a list), and used it to populate an
anonymous array reference, then dereferenced that reference. The
result is an actual array. An array in scalar context returns its
size.

Paul Lalli
 
A

Anno Siegel

Richard Harman said:
I suspected as much :)


Wow, according to perlfaq4 I'm doing all sorts of bad things, like
($exits) = grep { m/pattern/ } @array;

What do you think is wrong with that? It's a perfectly good idiom
to retrieve the first element of @array for which /pattern/ matches.
I use it all the time (unless I'm using List::Util::first).

Anno
 
P

Paul Lalli

Anno said:
What do you think is wrong with that? It's a perfectly good idiom
to retrieve the first element of @array for which /pattern/ matches.
I use it all the time (unless I'm using List::Util::first).

What's wrong with it is that the FAQ (which the regulars love to point
the newbies to) specifically says not to use it:

perldoc -q contain
<...>
Please do not use

($is_there) = grep $_ eq $whatever, @array;

or worse yet

($is_there) = grep /$whatever/, @array;

These are slow (checks every element even if the first
matches), inefficient (same reason), and potentially buggy
(what if there are regex characters in $whatever?).

Now maybe this particular FAQ is too paranoid or restrictive, or
problematic in other ways. But I really don't think we can have it
both ways - we can't be both chiding people for not looking at the FAQ
or following the FAQ's responses, and then telling them that there's
nothing wrong with something the FAQ says not to do.

Perhaps this particular FAQ could stand some scrutiny?

Paul Lalli
 
A

Anno Siegel

Paul Lalli said:
What's wrong with it is that the FAQ (which the regulars love to point
the newbies to) specifically says not to use it:

perldoc -q contain
<...>
Please do not use

($is_there) = grep $_ eq $whatever, @array;

or worse yet

($is_there) = grep /$whatever/, @array;

These are slow (checks every element even if the first
matches), inefficient (same reason), and potentially buggy
(what if there are regex characters in $whatever?).

Now maybe this particular FAQ is too paranoid or restrictive, or
problematic in other ways. But I really don't think we can have it
both ways - we can't be both chiding people for not looking at the FAQ
or following the FAQ's responses, and then telling them that there's
nothing wrong with something the FAQ says not to do.

Perhaps this particular FAQ could stand some scrutiny?

I think it could. While it is true that grep for a single element
can be inefficient, whether that is a real concern depends on a lot
of circumstances. Both methods are linear in the length of the list,
what factor you get depends on the statistics of the match. The
grep method may well be adequate.

The argument about $whatever containing regex characters is absolutely
besides the point. If it where valid, there ought to be a warning
against interpolating strings in a regex, ever.

It also ought to mention List::Util::first, which wasn't around when
the answer was written. I'll see what I can do.

Anno
 
A

Anno Siegel

Anno Siegel said:
Paul Lalli <[email protected]> wrote in comp.lang.perl.misc:

[FAQ How can I tell whether a certain element is contained in a list or
array?]
I think it could.

....or rather, some of it doesn't stand up to scrutiny.

Let's see if "FAQ" in the subject draws brian's attention. Otherwise
I'll jump in when it comes up regularly.

The FAQ in question is found through "perldoc -q contain". The first
part shows how to use hashes, arrays and bit vectors for the purpose,
nothing to change there. Then it goes on

Please do not use

($is_there) = grep $_ eq $whatever, @array;

or worse yet

($is_there) = grep /$whatever/, @array;

These are slow (checks every element even if the first matches), inef-
ficient (same reason), and potentially buggy (what if there are regex
characters in $whatever?). If you're only testing once, then use:

$is_there = 0;
foreach $elt (@array) {
if ($elt eq $elt_to_find) {
$is_there = 1;
last;
}
}
if ($is_there) { ... }

That part I'd replace with

These methods guarantee fast individual tests but require a re-organization
of the original list or array. They only pay off if you have to test
multiple values against the same array.

If you are testing only once, the standard module List::Util exports
the function C<first> for this purpose. It works like

sub first (&@) {
my $code = shift;
foreach (@_) {
return $_ if &{$code}();
}
undef;
}

but has a fast implementation in C.

If speed is of little concern, the common idiom is

($is_there) = grep $_ eq $whatever, @array;

It is slow (checks every element even if the first matches), but simple and
flexible. The variant

($first, @others) = grep $_ eq $whatever, @array;

allows to tell whether the match was unique.

Anno
 
A

Anno Siegel

Anno Siegel said:
Paul Lalli <[email protected]> wrote in comp.lang.perl.misc:

[FAQ How can I tell whether a certain element is contained in a list or
array?]
I think it could.

....or rather, some of it doesn't stand up to scrutiny.

The FAQ in question is found through "perldoc -q contain". The first
part shows how to use hashes, arrays and bit vectors for the purpose,
nothing to change there. Then it goes on

Please do not use

($is_there) = grep $_ eq $whatever, @array;

or worse yet

($is_there) = grep /$whatever/, @array;

These are slow (checks every element even if the first matches), inef-
ficient (same reason), and potentially buggy (what if there are regex
characters in $whatever?). If you're only testing once, then use:

$is_there = 0;
foreach $elt (@array) {
if ($elt eq $elt_to_find) {
$is_there = 1;
last;
}
}
if ($is_there) { ... }

That part I'd replace with

These methods guarantee fast individual tests but require a re-organization
of the original list or array. They only pay off if you have to test
multiple values against the same array.

If you are testing only once, the standard module List::Util exports
the function C<first> for this purpose. It works like

sub first (&@) {
my $code = shift;
foreach (@_) {
return $_ if &{$code}();
}
undef;
}

but has a fast implementation in C.

If speed is of little concern, the common idiom is

($is_there) = grep $_ eq $whatever, @array;

It is slow (checks every element even if the first matches), but simple and
flexible. The variant

($first, @others) = grep $_ eq $whatever, @array;

allows to tell whether the match was unique.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top