You've got two problems. First, you need the mySort
function in the super class to call the compare
function for the correct subclass. Second, you need the compare
function in the subclass to be able to receive the two elements it wants to compare from a call in a different package.
It's not clear whether you worked out a solution to the first problem, but one solution is to use UNIVERSAL::can
to find out the right comparison method.
package Fruit;
sub mySort {
my $self = shift;
my $compare_func = $self->can("compare");
@{$self->{itemList}} = sort $compare_func @{$self->{itemList}};
}
This will find the correct subclass compare
function and use it in the sort call.
Now the issue in the Apples::compare
function will be that when Fruit::mySort
is ready to compare a couple of elements, it will set the package variables $Fruit::a
and $Fruit::b
, not $Apples::a
and $Apples::b
. So your Apples::compare
function must be prepared for this. Here are a couple of solutions:
package Apples;
sub compare {
package Fruit;
$a->{mass} <=> $b->{mass};
}
or
sub compare {
$Fruit::a->{mass} <=> $Fruit::b->{mass}
}
or more defensively,
package Apples;
sub compare {
my $pkg = caller;
if ($pkg ne __PACKAGE__) {
no strict 'refs';
$a = ${"${pkg}::a"};
$b = ${"${pkg}::b"};
}
$a->{mass} <=> $b->{mass}
}
Update: I thought about making a subroutine attribute that would copy $a
and $b
values into the correct package, but after benchmarking it and thinking about alternatives, I decided against it. Here were my results for posterity:
Consider three sort routines (that might be in another package and hard to use from the current package)
sub numsort { $a <=> $b }
sub lexsort { $a cmp $b }
sub objsort { $a->{value} <=> $b->{value} }
Here are some ways we can make these packages accessible:
implement a subroutine attribute to prepare the $a
and $b
variables in the right package. Implementation is too long to include here, but the sub declaration would look like
sub numsort : CrossPkg { $a <=> $b }
rewrite the comparison function to compare $_[0]
and $_[1]
instead of $a
and $b
, and use a wrapper in the sort
call
sub lexcmp { $_[0] cmp $_[1] }
...
@output = sort { lexcmp($a,$b) } @input
Perform the sort call in the correct package, so it sets the correct $a
and $b
values.
@output = do { package OtherPackage; sort numsort @input };
And here are the benchmarking results. The local
method is the ordinary sort
call with no cross-package issues.
Rate attrib-numsort wrap-numcmp local-numsort repkg-numsort
attrib-numsort 1.17/s -- -90% -96% -96%
wrap-numcmp 11.6/s 885% -- -61% -64%
local-numsort 29.5/s 2412% 155% -- -8%
repkg-numsort 32.2/s 2639% 178% 9% --
Rate attrib-lexsort repkg-lexsort wrap-lexcmp local-lexsort
attrib-lexsort 3.17/s -- -12% -14% -17%
repkg-lexsort 3.60/s 13% -- -2% -5%
wrap-lexcmp 3.68/s 16% 2% -- -3%
local-lexsort 3.80/s 20% 6% 3% --
Rate attrib-objsort wrap-objcmp local-objsort repkg-objsort
attrib-objsort 1.22/s -- -81% -88% -89%
wrap-objcmp 6.32/s 417% -- -38% -44%
local-objsort 10.1/s 730% 61% -- -10%
repkg-objsort 11.3/s 824% 79% 11% --
Summary: overhead is less of a concern with lexsort
,
where each comparison takes more time. The attribute
approach is dead on arrival. Setting the package going into
the sort
call has the
best results -- more or less no overhead -- but it isn't
suitable for this application (in an object hierarchy).
Rewriting the comparison function and wrapping the function
in the sort
call isn't too bad of a performance drop-off,
and it works in an object hierarchy, so the final
recommendation is:
package Fruit;
sub compare { ... }
sub mySort {
my $self = shift;
@{$self->{itemList}} =
sort { $self->can("compare")->($a,$b) } @{$self->{itemList}};
}
package Apples;
our @ISA = qw(Fruit)
sub compare { $_[0]->{mass} <=> $_[1]->{mass} }
mySort()
on different subclasses, each with its owncompare()
. The answers so far look promising. – Natter