Reputation: 3432
I am trying to define an inline hash in a while each loop, my program is not throwing any errors but also doesn't execute the print statement. Is it possible to define an inline hash like below:
while (my (key, value) = each %{ (apple => "red", orange => "orange", grape => "purple")}) {
print "something";
}
or alternatively I can't while each loop to work if I directly call a sub in the each statement that returns a hash like the following:
sub returnsHash {
my %fruits = (
apple => "red",
orange => "orange",
grape => "purple",
);
return %fruits;
}
while (my (key, value) = each %{ returnsHash() }) {
print "something";
}
Upvotes: 3
Views: 1423
Reputation: 153
The while executes the expression on each loop, a ref to the hash works.
sub returnsHash {
my %fruits = (
apple => "red",
orange => "orange",
grape => "purple",
);
return \%fruits;
}
my $f = returnsHash() ;
while ( my ($key, $value) = each %{ $f } ) {
print "$key => $value\n";
}
what is needed is a foreach:
use v5.36 ;
no warnings qw(experimental::for_list);
sub returnsHash {
my %fruits = (
apple => "red",
orange => "orange",
grape => "purple",
);
return %fruits;
}
foreach my ($key, $value) ( returnsHash() ) {
print "$key => $value\n";
}
Upvotes: 0
Reputation: 66891
That can't be done since each only works as intended with an actual variable, a hash or an array. See the synopsis in docs. Same goes for keys and values.
It is the same with the second attempt, where the function is also called anew in every iteration.
Note that a sensible thing to try would be %{ {...} }
(and not %{ (...) }
) since the thing inside %{}
must be a hash reference. This applies to both attempts as the function returns a hash, whereby you get back a list of scalars. (This still wouldn't help, per the first statement.)
I am not sure what the need is for this, as a hash can be defined before the loop. Also, I'd like to suggest to carefully look at each
before using it since it comes with complexities.
I take it that you want to iterate over key-value pairs of a dynamically created list of such pairs. Here is a way to do that using a custom iterator (which wraps the hash iterator used by each
)
use warnings;
use strict;
use feature 'say';
my $hit = get_each_it(a => 1, b => 2, c => 3);
while (my ($k, $v) = $hit->()) {
say "$k => $v";
}
my ($k, $v) = $hit->(); # restarts the iterator
say "$k --> $v";
($k, $v) = $hit->(); # next (keeps state)
say "$k --> $v";
sub get_each_it {
my %h = @_;
return sub { return each %h }
}
The repeated and continued iteration (after the hash is exhausted or for individual calls) is a basic property of the hash iterator that each uses, and in doing that
So long as a given hash is unmodified you may rely on
keys
,values
andeach
to repeatedly return the same order as each other.
Please study carefully how this works.
See this article on Perl.com about iterators, with a number of examples. A detailed discussion of iterators is given in Iterator module, along with a tutorial. I don't know the module well but docs are worth reading; each and every warning and caveat applies to each
.
In case you don't need (or want) the capability to reset the iterator for continued iterations once the hash is exhausted, here is an alternative iterator from ikegami's comment using splice
sub get_each_it {
my @kv = @_;
return sub { return splice @kv, 0, 2 }
}
This doesn't get entangled with each
and it also iterates in the order of the submitted list.
Note that by properties of a closure each code reference returned by the generator still retains its own iterator, which maintains its state when invoked from various pieces of code. Use with care.
Also see an introductory note on closure in perlfaq7
Upvotes: 5
Reputation: 385917
A list/comma operator in scalar context evaluates to the result of the last item evaluated in scalar context. That means
each %{ apple => "red", orange => "orange", grape => "purple" }
is equivalent to
each %{ "purple" }
This is what both of your snippets are doing, but it's undesired, and it's a strict violation. (Always use use strict; use warnings qw( all );
!!!)
You are using a hash dereference (%{ ... }
), but you have no hash, much less a reference to a hash that you can dereference. To build a hash and return a reference to the hash, use { ... }
.
each %{ { apple => "red", orange => "orange", grape => "purple" } }
While that solves a problem, but just reveals another problem: You get an endless loop.
The iterator used by each
, keys
and values
is associated with the hash, not the operator. Since you are creating a new hash each time through the loop, you are creating a new iterator each time through the loop, so you will always get the first element of the newly created hash, and your loop will never end.
Since you have no need to look up items by key, I don't see why you're using a hash at all. You could use the following instead:
for (
[ apple => "red" ],
[ orange => "orange" ],
[ grape => "purple" ],
) {
my ($key, $val) = @$_;
...
}
The following is how you'd write the above if you got the list from a sub.
use List::Util qw( pairs );
for (pairs(f())) {
my ($key, $val) = @$_;
...
}
Both of those create many arrays, though. Since there's no issue with being destructive, I would use the following which avoids the issue:
{
my @kvs = f();
while ( my ($key, $val) = splice(@kvs, 0, 2) ) {
...
}
}
You could also use the following, but I think many would be confused by it:
for (
my @kvs = f();
my ($key, $val) = splice(@kvs, 0, 2);
) {
...
}
Upvotes: 12
Reputation: 37472
A possibility to do what you apparently want to do, would be using a for
loop. The (not so) anonymous hash goes in the initialisation expression. The test expression is the assignment of each
and the iteration expression stays empty.
#!/usr/bin/perl
use strict;
use warnings;
for (my %h = (
apple => 'red',
orange => 'orange',
grape => 'purple',
);
my ($key, $value) = each(%h);
) {
print("$key: $value\n");
}
This can actually be seen as a sort of a while
loop with an initialisation local to the loop. %h
is only in scope in the loop. So it's not anonymous to the for
loop and can be used with each
but ceases to exist after the loop is done.
Upvotes: 1