Perl variable variable issue

So I have a Perl class. It has a sort() method, and I want it to be more or less identical to the built-in sort() function:

 $object->sort(sub ($$) { $_[0] <=> $_[1] }); 

But I can not:

 $object->sort(sub { $a <=> $b }); 

Due to visibility. But the List :: Util module does this with reduce() . I looked at the List :: Util module and they did some nasty things with no strict 'vars' to make this happen. I tried this, but to no avail.

As far as I understand, reduce() works the way it happens, because it is exported to the corresponding namespace, and therefore my class cannot do this, since the function is pretty firmly in another namespace. Is this right, or are there some (undoubtedly more disgusting and unscrupulous) ways to do this in my situation?

+7
scope class perl
source share
3 answers

Well, the other two answers are half right. Here's a working solution that really sorted:

 package Foo; use strict; use warnings; sub sort { my ($self, $sub) = @_; my ($pkg) = caller; my @x = qw(1 6 39 2 5); print "@x\n"; { no strict 'refs'; @x = sort { local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); $sub->(); } @x; } print "@x\n"; return; } package main; use strict; use warnings; my $foo = {}; bless $foo, 'Foo'; $foo->sort(sub { $a <=> $b }); # 1 6 39 2 5 # 1 2 5 6 39 

Presumably, you would sort some data that is actually part of the object.

You need caller magic so that you localize $a and $b in the caller's package, where Perl will look for them. It creates global variables that exist only when this sub is called.

Note that you will get a "name used only once", with warnings ; I'm sure there are some hoops that you can jump over to somehow avoid this.

+8
source share

You can use Sub :: Identify to find out the package (which it calls stash_name ) associated with coderef. Then install $ a and $ b in this package as needed. You may need to use no strict 'refs' in your method to make this work.

Here's Evee's answer modified to work in general:

 use strict; use warnings; package Foo; use Sub::Identify 'stash_name'; sub sort { my ($self, $sub) = @_; my $pkg = stash_name($sub); my @x = qw(1 6 39 2 5); print "@x\n"; { no strict 'refs'; @x = sort { local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); $sub->(); } @x; } print "@x\n"; return; } package Sorter; sub compare { $a <=> $b } package main; use strict; use warnings; my $foo = {}; bless $foo, 'Foo'; $foo->sort(\&Sorter::compare ); $foo->sort(sub { $b <=> $a }); 
+3
source share

You can use the local operator to set values ​​for $a and $b for the duration of the subroutine call:

 sub sample { my $callback = shift; for (my $i = 0; $i < @_; $i += 2) { local ($a, $b) = @_[$i, $i + 1]; $callback->(); } } sample sub { print "$a $b\n" }, qw(abcdefghij); 

If you have a regular routine, not a method, you can make it even more like sort , so you don't need to use sub in front of your callback function. Use the function prototype:

 sub sample (&@) 

Then you call it like this:

 sample { print "$a $b\n" } qw(abcdefghij); 

The methods, however, are independent of prototypes.

+1
source share

All Articles