NB: the closure featured in this question is just a convenient example; the one I'm actually working with is substantially more complex than this. IOW, please disregard the details of this closure; all that matters, AFAICT, is that it refers to lexical variables in the parent scope.
I want to redefine the sub foo
below so that the first argument in the call to List::Util::reduce
is replaced with a reference to a nested closure.
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 was this:
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 results in a warning saying that Variable "$y" will not stay shared
.
I've seen this sort of error before, and the only way I know around it is to replace the nested sub
definition with an anonymous sub assigned to a variable. Therefore, I tried this instead:
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)
.
This I really don't understand. Why would passing a subref as the first argument to reduce
not be supported?
PS: The following does work:
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's wrong with using a subref as the first argument to List::Util::reduce
(e.g. is there some technical impediment to supporting this variant?); and (b) is there a more direct approach (than { $z->( $a, $b ) }
) to passing to List::Util::reduce
a nested closure?
Following are some useful points which also form necessary conditions for implementing closures in python: There should be nested function i.e. function inside a function. The inner function must refer to a non-local variable or the local variable of the outer function. The outer function must return the inner function.
It is a smart and concise way of creating lists by iterating over an iterable object. Nested List Comprehensions are nothing but a list comprehension within another list comprehension which is quite similar to nested for loops.
Following is an example of a nested function accessing a non-local variable. We can see that the nested printer () function was able to access the non-local msg variable of the enclosing function.
A function defined inside another function is called a nested function. Nested functions can access variables of the enclosing scope. In Python, these non-local variables are read-only by default and we must declare them explicitly as non-local (using nonlocal keyword) in order to modify them.
List::Util::reduce
uses a Perl prototype which specifies a compile-time check on the parameters passed in a call. Passing a scalar variable that contains a subroutine reference cannot be verified at compile time so Perl disallows it
This is also the route to the the solution. It is usually considered bad practice to call a subroutine using an ampersand character &
but in this case it will defeat the prototype checking, which is what 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 isn't possible in general, as a prototype can do such things as force an array or hash to be passed by reference, or force scalar context on an expression, and calling such a subroutine using &
will disable these behaviours as well. But reduce
has a prototype of &@
which means a block or sub { }
(the sub
part is optional here) followed by a list of scalars, which is the normal behaviour for a subroutine without a prototype, so the only effect is to allow $z
to be passed for the subroutine reference
reduce
has a prototype of &@
. That means calls must use one of the following syntax:
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 could use any of the following equivalent statements:
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 of overriding the prototype.
my $z = sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a };
return &reduce($z, 0, 0 .. $#$y);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With