How to use nested closure as the first argument of List :: Util :: reduce?

NB: the closure shown in this question is just a convenient example; the one I actually work with is significantly more complicated than that. IOW, please ignore the details of this closure; all that matters, AFAICT, is that it refers to lexical variables in the parent area.


I want to override sub foo below so that the first argument in the List::Util::reduce call is replaced with a nested closure reference.

 use strict; use warnings FATAL => 'all'; use List::Util; sub foo { my ( $x, $y ) = @_; return List::Util::reduce { $y->[ $b ]{ $x } ? $a + ( 1 << $b ) : $a } 0, 0 .. $#$y; } 

My first attempt:

 sub foo { my ( $x, $y ) = @_; sub z { our ( $a, $b ); return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a; } return List::Util::reduce \&z, 0, 0 .. $#{ $y }; } 

... but this leads to a warning that Variable "$y" will not stay shared .

I have seen such an error before, and the only way I know around it is to replace the nested sub definition with the anonymous submenu assigned to the variable. So I tried this:

 sub foo { my ( $x, $y ) = @_; my $z = sub { our ( $a, $b ); return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a; }; return List::Util::reduce( $z, 0, 0 .. $#{ $y } ); } 

Now the error says Type of arg 1 to List::Util::reduce must be block or sub {} (not private variable) .

I really do not understand this. Why isn't passing subref as the first argument to reduce ?


PS: the following works:

 sub foo { my ( $x, $y ) = @_; my $z = sub { our ( $a, $b ); return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a; }; return List::Util::reduce { $z->( $a, $b ) } 0, 0 .. $#{ $y }; } 

... but I really would like to know (a) what is wrong with using subref as the first argument for List::Util::reduce (for example, is there any technical obstacle to supporting this option?); and (b) is there a more direct approach (than { $z->( $a, $b ) } ) to switching to List::Util::reduce nested closure?

+5
source share
2 answers

reduce has a prototype &@ . This means that calls must use one of the following syntaxes:

 reduce BLOCK LIST reduce sub BLOCK, LIST # The first form is short for this. reduce \&NAME, LIST reduce \&$NAME, LIST # Same as third form, but via reference (short form) reduce \&BLOCK, LIST # Same as third form, but via reference (long form) 

You can use any of the following equivalent operators:

 return reduce { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a } 0, 0 .. $#$y; 

 # Long way of writing the first. return reduce(sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a }, 0, 0 .. $#$y); 

 my $z = sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a }; return reduce(\&$z, 0, 0 .. $#$y); 

You also have the option to override the prototype.

 my $z = sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a }; return &reduce($z, 0, 0 .. $#$y); 
+3
source

List::Util::reduce uses a Perl prototype that defines a compile-time check of the parameters passed in the call. Passing a scalar variable that contains a subroutine reference cannot be checked at compile time, so Perl forbids it

It is also a way to a solution. It is generally considered improper practice to call a subroutine using the ampersand character & , but in this case it will defeat the prototype check, which is required

 sub foo { my ( $x, $y ) = @_; my $z = sub { our ( $a, $b ); return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a; }; return &List::Util::reduce( $z, 0, 0 .. $#{ $y } ); # Defeat prototype } 

Note that this is not possible at all, since the prototype can do things such as forcing the transfer of an array or hash by reference or forcing the scalar expression context, and calling such a routine with & will also disable these behaviors. But reduce has a prototype &@ , which means a block or sub { } (here the sub part is optional), followed by a list of scalars, which is normal behavior for a routine without a prototype, so the only effect is passing $z to specify the routine

+4
source

Source: https://habr.com/ru/post/1245822/


All Articles