Given a code reference and a list of references to arrays, I'd like to select one item from each array and pass aliases to each into the referenced subroutine. For example, to select the first item from each referenced array, I can use:
sub mapfirst { my( $sub )= shift( @_ ); &$sub( map { $_->[0] } @_ ); # ^^^^^^^^^^^^^^^^^^ Here is the selection code } my @a= ( 1..3 ); my @b= ( 4..6 ); my @c= ( 7..9 ); sub addten { $_ += 10 for @_ } mapfirst( \&addten, \@a, \@b, \@c ); mapfirst( sub { print "@_\n" }, \@a, \@b, \@c );
but addten() gets passed copies of $a[0], $b[0], and $c[0] so it is temporary values that get 10 added to them and then thrown away and the output is "1 4 7".
Only copies get passed in because map { $_[0] } @_ returns a list of copies, not a list of aliases. I tried a lot of tricks to work around this "problem" and was getting convinced that there wasn't a way to do this in Perl.
I know I could solve this problem by using an XS module to construct an array of aliases, so such solutions aren't interesting (this is a quest for knowledge, not an attempt to solve a practical problem).
I didn't finish composing this as a SoPW before I had to move to other tasks and while fetching lunch I realized that I should mention the unsatisfactory solution of:
if( 0 == @_ ) { &$sub(); } elsif( 1 == @_ ) { &$sub( $_[0][0] ); } elsif( 2 == @_ ) { &$sub( $_[0][0], $_[1][0] ); } elsif( 3 == @_ ) { &$sub( $_[0][0], $_[1][0], $_[2][0] ); # etc to some arbitrary maximum number of arguments }
and that lead me to realize that I could get what I want using eval:
eval "&\$sub( " . join( ", ", map "\$_[$_][0]", 0..$#_ ) . " )"
which also wasn't that appealing.
I still hadn't finished composing when a new idea came to me. It took a bit of fiddling but looked like it might work and testing showed that it does work. The heart of this (whacky) idea is
$av= sub { \@_ }->( @$av, $_->[0] ) for @_;
as demonstrated by modifying my first example:
sub mapfirst { my( $sub )= shift( @_ ); my $av= []; $av= sub { \@_ }->( @$av, $_->[0] ) for @_; &$sub( @$av ); ## &$sub( map { $_->[0] } @_ ); } my @a= ( 1..3 ); my @b= ( 4..6 ); my @c= ( 7..9 ); sub addten { $_ += 10 for @_ } mapfirst( \&addten, \@a, \@b, \@c ); mapfirst( sub { print "@_\n" }, \@a, \@b, \@c );
which now outputs "11 14 17".
BTW, this all came about because someone asked (in the CB) how to swap the values of two variables. That got me thinking about ways to generalize
( $x, $y )= ( $y, $x );
such that you don't have to repeat the variable names. For example, like this:
sub swapPairs { @_[ map $_^1, 0..$#_ ]= @_ }
For some reason, however, my mind jumped to @_ = reverse @_, but I knew that wouldn't work because assigning to @_ doesn't modify the aliases contained in @_ like assigning to each $_[$i] does. But I can get around that with
$_= $something for @_;
But I need to loop over two different lists. Well, I wrote Algorithm::Loops in large part to allow iterating over more than one list at a time so, mostly as a joke, I proposed a "solution" for swapping values as:
use Algoritm::Loops 'MapCarE'; sub swapValues { MapCarE { $_[0]= $_[1] } \@_, [reverse @_] }
and then tested it to make sure it worked.
I wasn't that surprised that it didn't work, but I was curious enough to wonder why it didn't. Looking into it, I couldn't see where the aliases turned into copies (despite the numerous layers they end up passing through) and I got even more interested. I finally realized that map was the culprit and, since I'd narrowed it down to just the one culprit, I wanted to "fix" it.
That turned out to be quite a problem, so I thought it might be a fun SoPW (sort of like a golf puzzle, an intellectual exercise with little practical value).
But now I've got an interesting solution and I'm considering adding new flavors of MapCar to Algorithm::Loops that work on aliases (probably at a minor performance penalty).
- tye
|
|---|