Perl / perl5

🐪 The Perl programming language
https://dev.perl.org/perl5/
Other
1.92k stars 551 forks source link

Missing support for undef in for_list assignments #22438

Open book opened 2 months ago

book commented 2 months ago

This post https://www.effectiveperlprogramming.com/2024/06/iterate-over-multiple-elements-at-the-same-time/ exposes some of the shortcomings of the for_list feature:

No placeholders (yet)

So far, this new syntax doesn’t have a way to skip values. In a normal list assignment, you discard a value coming from the right hand list with a literal undef:

my( $s, undef, $t ) = @animals

Try that in the for list and you get a syntax error:

foreach my( $s, undef, $u ) ( @animals ) {  # ERROR!
    say "$s ^^^ $u";
}

The rest of the post comments on other missing capabilities of the feature:

The conclusion implies we could invest a little more work on the for_list feature:

The experimental for_list feature lets you take multiple elements of the list in each iteration. This doesn't yet handle many of the list assignment features that would make this as useful as people will want it to be.

leonerd commented 2 months ago
  • operating on a hash could be optimized to not build the whole list of values, but iterate on the hash directly instead

Another thing that could definitely be optimised internally is applying a similar optimisation to the FOR_ARRAY case, when iterating two at a time over a call to builtin::indexed on same array.

I.e. both of these should be done efficiently without a temporary list:

foreach my $elem ( @array ) { ... }

foreach my ( $idx, $elem ) ( builtin::indexed @array ) { ... }
guest20 commented 2 months ago

aliasing

You only get that elsewhere in the language by not unpacking @_ (subs) or $_ (loops). There's no destructuring syntax that can give you aliases. Elsewhere the "= is copy" rule thwarts unpacking an alias by copying the value when you unpack it/them into scalar(s).

sub frobulate {
my ($copy, @copies) = @_;
# if you wanna do alias stuff you have
# to assign to $_[xx] but you can 
# shallow modify $copy/@copies freely 
...
}
guest20 commented 2 months ago

When looping over hash(-ref)es you gotta do something about key randomisation anyway, and then you gotta zip them back into a list for for_list to unpack, so you'd have to do

for my ($k, $v) ( map { $_, $hash->{$_} } sort keys %$hash) { 
  ... 
}

at that point you're much more likely to get this patch past a code review:

for my $k (sort keys %$hash) { 
  my $v = $hash->{$k};
  ...
}
ap commented 2 months ago

@leonerd

I.e. both of these should be done efficiently without a temporary list:

foreach my $elem ( @array ) { ... }

foreach my ( $idx, $elem ) ( builtin::indexed @array ) { ... }

I thought the first one was already optimised?


@guest20

There's no destructuring syntax that can give you aliases.

Sure there is (at least experimentally – but which is all @book was referring to anyway):

use 5.022;
use feature 'refaliasing';
my @array = ( "a".."c" );
\my ( $x ) = \( @array );
$x = "foo";
say for @array;

@book

operating on a hash could be optimized to not build the whole list of values, but iterate on the hash directly instead

My gut reaction is that this might not actually be a good idea. People use for ( keys %hash ) as a best practice because the fact that the each iterator is global makes it susceptible to action at a distance.

(E.g.: if you last of out of a while ( my ( $k, $v ) = each %hash ) loop, without exhausting the iterator, and then the hash is passed to somewhere else that tries to iterate the hash the same way, then the other loop might inadvertently skip the keys you’d already iterated. Or, if your while/each loop passes the hash to something that calls keys on it, you have an infinite loop that restarts over and over. Etc.)

We already have while ( my ( $k, $v ) = each %hash ). Is it useful to have a different spelling of the exact same thing?

Plus, “aliasing” the key won’t actually alias the key. This is true for keys (which returns copies) vs values (which returns aliases). It’s even true for each - which produces a copy of the key and an alias of the value! This is slightly more awkward to demonstrate, but you can:

use 5.012;
my %h = map +( $_ => $_ ), "a".."c";
while ( my ( $key_ref, $value_ref ) = \each %h ) {
  ( $$key_ref, $$value_ref ) = qw( foo bar );
}
say for %h;

[ A side note here is that you can’t usefully do while ( \my ( $key, $value ) = \each %h ) because each returns an empty list when the iterator runs out, which \my ( ... ) winds up treating as an error: Assigned value is not a reference. Looks like refaliasing doesn’t vivify undefs into references, which off the cuff I’m not sure is a design flaw or correct choice or just an implementation bug or what. ]

leonerd commented 2 months ago

@leonerd

I.e. both of these should be done efficiently without a temporary list:

foreach my $elem ( @array ) { ... }

foreach my ( $idx, $elem ) ( builtin::indexed @array ) { ... }

I thought the first one was already optimised?

Yes, it is. Sorry, bad wording on my part. I meant, by comparison to the first (which is already optimised), the second should be optimised the same way.

guest20 commented 2 months ago

@ap having very recently learnt about feature refaliasing I can confidently say I hate it.

use 5.022; say $^V; 
use feature 'refaliasing';

my $whatever = "whatever";
\(my $thing) = \$whatever;

$thing = "Oh no!";

say "$thing $whatever"
Aliasing via reference is experimental at main.pl line 5.
v5.34.0
Oh no! Oh no!

It breaks the = does copy rule, hard. This is super cursed.