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        

Replies are listed 'Best First'.
Re: Getting a list of aliases to selected items from a list of array references
by kvale (Monsignor) on May 14, 2004 at 23:24 UTC
    If you relax the requirements a little so that the sub operates on references instead of aliases, this would seem to work:
    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 join " ", map{$$_} @_; print "\n" }, \@a, \@b, \ +@c );

    -Mark

Re: Getting a list of aliases to selected items from a list of array references
by NetWallah (Canon) on May 15, 2004 at 06:08 UTC
    I struggled a bit before I got this code to work - the code below includes kvale's Reference-passing method (shortened by 2 bytes of code), and an alternative using closures and lvalue subs - not the most elegant, but it was fun experimenting with them..
    sub map_With_Ref { my( $sub )= shift( @_ ); &$sub( map { \$_->[0] } @_ ); # ^^^^^^^^^^^^^^^^^^ Here is the selection code } sub map_With_Closure { my( $sub )= shift( @_ ); my @closures; foreach my $aref(@_){ push @closures, sub :lvalue{ $aref->[0] }; } &$sub( @closures ); # I had trouble attempting to dynamically build the equivalent of +the # @closures array using the "map {BLOCK}" syntax - apparently that # conflicts with lvalue subs. } my @a= ( 1..3 ); my @b= ( 4..6 ); my @c= ( 7..9 ); sub addten_Ref { $$_ += 10 for @_ } sub addten_With_Closure { &$_() += 10 for @_ } print "--- REF test --\n"; map_With_Ref( \&addten_Ref, \@a, \@b, \@c ); map_With_Ref( sub { print "$$_\n" for @_ }, \@a, \@b, \@c ); print "--- Closure test --\n" ; map_With_Closure( \&addten_With_Closure, \@a, \@b, \@c ); map_With_Closure( sub { print &$_() . "\n" for @_ }, \@a, \@b, \@c );
    ################### --Output -- --- REF test -- 11 14 17 --- Closure test -- 21 24 27
    If anyone is successful in enclosing the lvalue sub inside a "map" statement, I'd love to see the code.

    Offense, like beauty, is in the eye of the beholder, and a fantasy.
    By guaranteeing freedom of expression, the First Amendment also guarntees offense.
Re: Getting a list of aliases to selected items from a list of array references
by ysth (Canon) on May 16, 2004 at 17:52 UTC
    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..$#_ ) . " )"
    You could do (untested, and probably some experimentation needed to find a good cutoff point):
    if ($#_ <= 255) { my $lim = $#_; &$sub(($_[0][0],$_[1][0],$_[2][0],$_[3][0],$_[4][0], $_[5][0],$_[6][0],$_[7][0],$_[8][0],$_[9][0], ... $_[255] )[0..$lim]); $#_ = $lim # remove autovivified arrayrefs } else { eval "&\$sub( " . join( ", ", map "\$_[$_][0]", 0..$#_ ) . " )"; }

      Sort of combining this idea with my idea, I could unroll the loop, perhaps reducing the performance penalty (or perhaps not):

      my $aPush= sub { \@_ }; my $av= []; while( 9 < @_ ) { $av= $aPush->( @$av, $_[0][0], $_[1][0], $_[2][0], $_[3][0], $_[4][0], $_[5][0], $_[6][0], $_[7][0], $_[8][0], $_[9][0] + ); splice @_, 0, 10; } while( 2 < @_ ) { $av= $aPush->( @$av, $_[0][0], $_[1][0], $_[2][0] ); splice @_, 0, 3; } $av= $aPush->( @$av, $_->[0] ) for @_;

      or combine more of your idea in but still not need string eval...

      - tye        

        I don't like thrashing @_ because I'm weird, so how about something like this?

        my $aPush = sub { \@_ }; my $av = []; for (my $index = 0;$index < @_; $index += 16) { my $length = $#_ - $index; $av = $aPush->(@$rv, ($_[$index][0],$_[$index+1][0], $_[$index+2][0],$_[$index+3][0],$_[$index+4][0], $_[$index+5][0],$_[$index+6][0],$_[$index+7][0], $_[$index+8][0],$_[$index+9][0],$_[$index+10][0], $_[$index+11][0],$_[$index+12][0],$_[$index+13][0], $_[$index+14][0],$_[$index+15][0] )[ 0..($length < 16 ? $length:15) ] ); }

        Update: After benchmarking this against what you have in the previous post, yours is definitely faster.

        antirice    
        The first rule of Perl club is - use Perl
        The
        ith rule of Perl club is - follow rule i - 1 for i > 1

Re: Getting a list of aliases to selected items from a list of array references
by tinita (Parson) on May 15, 2004 at 15:53 UTC
    just another suggestion. haven't read the OP too carefully, so maybe that doesn't fit your needs, but my first thought was to use
    ( map { $sub->($_->[0]) } @_ ); # instead of &$sub( map { $_->[0] } @_ );
Re: Getting a list of aliases to selected items from a list of array references
by NetWallah (Canon) on May 15, 2004 at 21:39 UTC
    I think I've found what I would consider an "elegant" solution to this.

    Update: Apologies - my test was flawed - I copied & pasted the lines without intended modifications.

    The "map_twice" code does not work as intended- it does not provide the simple, reference-free interface I'm looking for (Second map is unnecessary, and this case degerates to the same as the "ref" case). That code was not being tested.

    The code below incorporates previous code on this topic, including tinita's suggestion (replacing 'map' with 'for', since the returned array is not used.)

    I'm calling the new method "map_twice", which will now maintain aliases, and pass them into the subrouting parameter. No complications with closures and lvalu subs.

    sub map_With_Ref { # kvale ... my( $sub )= shift( @_ ); &$sub( map { \$_->[0] } @_ ); # ^^^^^^^^^^^^^^^^^^ Here is the selection code } sub map_With_Closure { # NetWallah my( $sub )= shift( @_ ); my @closures; foreach my $aref(@_){ push @closures, sub :lvalue{ $aref->[0] }; } &$sub( @closures ); # I had trouble attempting to dynamically build the equivalent of +the # @closures array using the "map {BLOCK}" syntax - apparently that # conflicts with lvalue subs. } sub map_pass_as_Param{ #tinita's code... my( $sub )= shift( @_ ); &$sub( $_->[0]) for @_; # Call once for each param passed } sub map_twice{ # NetWallah my( $sub )= shift( @_ ); &$sub( map {$_} map { \$_->[0] } @_ ); } my @a= ( 1..3 ); my @b= ( 4..6 ); my @c= ( 7..9 ); sub addten_Ref { $$_ += 10 for @_ } sub addten_With_Closure { &$_() += 10 for @_ } sub addten_call_Sub { $_[0]+=10} # Only ONE param passed, multiple cal +ls sub addten_array { $_ += 10 for @_ } print "--- REF test --\n"; map_With_Ref( \&addten_Ref, \@a, \@b, \@c ); map_With_Ref( sub { print "$$_\n" for @_ }, \@a, \@b, \@c ); print "--- Closure test --\n" ; map_With_Closure( \&addten_With_Closure, \@a, \@b, \@c ); map_With_Closure( sub { print &$_() . "\n" for @_ }, \@a, \@b, \@c ); print "--- Calling Passed Sub test --\n"; map_pass_as_Param( \&addten_call_Sub, \@a, \@b, \@c ); map_pass_as_Param( sub { print shift . "\n" }, \@a, \@b, \@c ); print "--- Double-mapping test --\n"; # "Map-twice" should have been called here **** map_pass_as_Param( \&addten_array, \@a, \@b, \@c ); map_pass_as_Param( sub { print "@_\n" }, \@a, \@b, \@c );
    --- OUTPUT---- --- REF test -- 11 14 17 --- Closure test -- 21 24 27 --- Calling Passed Sub test 31 34 37 --- Double-mapping test -- 41 44 47

    Offense, like beauty, is in the eye of the beholder, and a fantasy.
    By guaranteeing freedom of expression, the First Amendment also guarntees offense.
      I had trouble attempting to dynamically build the equivalent of the @closures array using the "map {BLOCK}" syntax - apparently that conflicts with lvalue subs. }
      sub : attribtute ... will be parsed at the beginning of a statement as "sub" being a label, and attribute... the code to run. Put a + in front of sub to disambiguate. Witness:
      $ perl5.8.4 -we'sub xyzzy { print "huh?" } sub : xyzzy; shift() and go +to "sub"' 1 2 huh?huh?huh?
      The makings of a fine japh here...
        Excellent! - Many thanks, ysth - that worked great!
        # Replace @closures usage by this line in "sub map_With_Closure" &$sub( map {my $x=$_; +sub:lvalue{$x->[0]} }@_ ); # "+" required in front of sub, to avoid perl's mininterpration # of "sub" being a label, and attribute-- thanks ysth!
        The only minor gripe with this code is that it requires the use of the temporary variable $x , since $_ is local to the lvalue sub, and does not inherit from the "map".

        Offense, like beauty, is in the eye of the beholder, and a fantasy.
        By guaranteeing freedom of expression, the First Amendment also guarntees offense.