perl5 icon indicating copy to clipboard operation
perl5 copied to clipboard

[EXPERIMENT] Iterating over every N elements in a list

Open KES777 opened this issue 3 years ago • 15 comments

I would like to do:

my $list = [ key => 'value', k2 => 2, k3 => 3 ];
for my( $key, $value ) ( @$list ) {
   ...
}

I do not want to use hacks listed here

Then I found similar idea at this question

for i,k in ???:
    ...

Notice, how many people are interested in similar question: 2500 votes up, 1 000 000+ views!

But there is also no solution from the box =(

But perl is better it will allow just:

for my( $first, $second, $n ) ( @list ) {
    ...
}

or any number of elements we want, will it? ;-)

KES777 avatar Apr 24 '21 16:04 KES777

Previously discussed at https://www.nntp.perl.org/group/perl.perl5.porters/2017/03/msg243848.html. I would like this syntax as well.

Grinnz avatar Apr 24 '21 17:04 Grinnz

it makes more sense with hashes (and hashrefs) where there is each operator (as used in while loop conditional)

rwp0 avatar Apr 24 '21 18:04 rwp0

With core-shipped modules, the neatest that can currently be done is probably List::Util::pairs:

use List::Util 'pairs';

foreach ( pairs %$hash ) {
  my ($k, $v) = @$_;
  ...
}

An imagined hypothetical syntax involving foreach my(VARLIST) (LIST) { BLOCK } would probably look like:

foreach my ($k, $v) ( %$hash ) {
  ...
}

It's a small improvement but nothing too ground-breaking. I don't imagine any back-compat issues because currently the proposed syntax is an error, albeit a weirdly-worded one:

$ perl -ce 'foreach my ($k, $v) (one=>1, two=>2) { }'
Missing $ on loop variable at -e line 1.

leonerd avatar Apr 24 '21 19:04 leonerd

Two lists in a row look confusing when syntax is only parentheses, no real separator.

It could be e.g. foreach(;){} or foreach(;;){} syntax (semicolon as separator), if foreach and for would be different, but they are same, they are alliases. E.g. it could be foreach( @binding_variable ; @array ; $position_increment ){}, where @binding_variable was e.g. my( $key, $value ) and $position_increment was 2, or e.g. @binding_variable was my( $left, $middle, $right ), and $position_increment was e.g. 1, which mean that "iterator" advance by 1, e.g. one can find how many hills ($_[ $i - 1 ] < $_[ $i ] > $_[ $i + 1 ]) the array contains. It would be similar to "@integer_array" =~ m/ (?= (\d+)[ ](\d+)[ ](\d+) ) (?{ compare_them }) (*FAIL) /x.

But instead of foreach(;;){} (which is C style loop) it can be a confusing foreach()()(){}, where third () if omitted evaluates to default.

rsFalse avatar Apr 25 '21 10:04 rsFalse

Could sound crazy, but how about:

forall( @list ) {
  my( $i1, $i2, $3 ) =  (shift, shift, shift);
}

Looks like usual subroutine call and subroutine implementation at one place. @list is bound to @_ and we even can remove items dynamically.

forall will call BLOCK while there are elements and we can shift as many elements as we want.

It seems it could be implemented as module without any problem. Drawback here could be performance issue while coping @list

KES777 avatar May 18 '21 15:05 KES777

If anything, that should put them in a lexically declared array. We don't need to abuse more global variables.

forall my @items (@list) { ...

But I don't really see the point of this. You can already just shift items off @list.

Grinnz avatar May 18 '21 15:05 Grinnz

Having shift change its behaviour inside a forall block would be kinda weird. But currently I don't see the difference from the tried-and-tested:

while(@list) {
   my ($x, $y, $x) = (shift @list, shift @list, shift @list);
   ...
}

which admittedly is a little repetative. OK how about

while(@list) {
   my ($x, $y, $z) = splice @list, 0, 3;
   ...
}

Or lets invent an shiftn which just eats the right number of items already:

while(@list) {
   my ($x, $y, $z) = shiftn @list;
   ...
}

Already many ideas that are very close to your suggestion.


Edited: renamed shiftn, from previous nshift idea

leonerd avatar May 18 '21 15:05 leonerd

@Grinnz : at your case @items will grab all items from @list

@leonerd: while( @list ){ ... } its great. Could you add this to documentation for List::Util?

right number do you mean that shiftn will shift as many element as there on left side of =? If so it will be nice.

KES777 avatar May 18 '21 15:05 KES777

@leonerd: while( @list ){ ... } its great. Could you add this to documentation for List::Util?

Why? It shouldn't be List::Util's job to teach users basic Perl.

leonerd avatar May 18 '21 15:05 leonerd

If anything, this would be suitable for a new entry in https://perldoc.perl.org/perlfaq4#Data:-Arrays

Grinnz avatar May 18 '21 15:05 Grinnz

@leonerd : yes, List::Util should not teach, but this module is a bundle of functions to work with list. So at least it must mention while( @list ) { ... } to deal with N elements and provide a link to appropriate documentation.

IMHO: Despite on I am an old perl programmer I never see this great construction before. I did not know this BASIC tool before

while(@list) {
   my ($x, $y, $z) = splice @list, 0, 3;
   ...
}

KES777 avatar May 18 '21 15:05 KES777

We're using this suggestion to trial an RFC process.

nwc10 avatar Jun 08 '21 11:06 nwc10

Or lets invent an nshift which just eats the right number of items already:

my ($x, $y, $z) = nshift @list;

Cool idea, I guess it has a lot of edge cases tho:

my @.***)= nshift @list;

It would be neat in general to be able to do this. Maybe a special prototype that pushes the number of arguments that the result will be assigned to into the stack automagically?

cheers, Yves

-- perl -Mre=debug -e "/just|another|perl|hacker/"

demerphq avatar Jun 08 '21 14:06 demerphq

https://perldoc.perl.org/5.36.0/perldelta#iterating-over-multiple-values-at-a-time-(experimental)

KES777 avatar Jun 09 '22 20:06 KES777

Can we close this issue, now that @leonerd committed https://github.com/Perl/perl5/commit/4b6ad5110c29c13149771584eabe8b42feb5c63c making the feature no longer experimental?

rwp0 avatar Apr 11 '24 21:04 rwp0