I'm trying to make a sort function available in one of my (object-oriented) packages that accepts a block and makes available $a and $b like the standard Perl sort
.
First, a simplified version of what I'm trying to do in the package that contains the wrapped sort function:
# In package My::Object
sub sort {
my $self = shift;
my $block = \&{shift @_};
return sort $block @{$self->{arrayRef}}; # I want to use the passed in block with array data contained in this object
}
And then an example of a client passing a block which defines the comparitor to run for the sort:
my $obj = My::Object->new([3, 1, 5, 6, 2, 4]); # As an example, these values will be come arrayRef from above
my @sortedVals = $obj->sort({ $a < $b });
Is there a way to do what I'm trying to do while still being able to use Perl's sort
?
Mostly.
To use the bare-block-as-subroutine syntax, you need to use the &
prototype. Generally you should avoid prototypes, but passing a subroutine as a bare block is one of the few times this is acceptable. Unfortunately, because they must be determined and applied at compile time, prototypes do not work on methods. So you have to use the full anonymous subroutine syntax, sub { ... }
.
my @sortedVals = $obj->sort(sub { $a <=> $b });
$a
and $b
are globals of the package the sort subroutine was declared in (let's say this is Some::Caller
). When run inside your class, sort will set $My::Object::a
and $My::Object::b
but the subroutine will be looking for $Some::Caller::a
and $Some::Caller::b
. You can work around this by aliasing their $a
and $b
to your $a
and $b
.
sub sort {
my $self = shift;
my $block = shift;
no strict 'refs';
local *{caller.'::a'} = *a;
local *{caller.'::b'} = *b;
return sort $block @{$self->{arrayRef}};
}
local
makes a temporary copy of a global for the duration of the block and this includes other subroutines called, so this will effect the sort. Then when the method is done the value will revert to what it was.
Perl globals are stored in typeglobs which contain all the global variables with the same name. *a
contains $a
and @a
and %a
. By copying the caller's typeglob, when $My::Object::a
changes, the caller's $a
will change as well. They are aliases.
The *{...}
syntax lets you get at a global variable by name using another variable or expression. This is called a symbolic reference. Now we can get at the caller's *a
. Using this syntax is usually a mistake, so you have to turn off strict
else Perl won't let you do it.