NAME

Foreign::Sort - subroutine attribute to allow call to sort routine from other package

VERSION

0.01

SYNOPSIS

package X1;
use Foreign::Sort;
sub by_middle : Foreign { 
    (substr($a,4) // "") cmp (substr($b,4) // "")
       ||      $a cmp $b 
}

package X2;
@env_keys = sort X1::by_middle keys %ENV;

THE PROBLEM

The builtin "sort" function takes an optional subroutine name to use as a comparison function. Just before calling the comparison function, Perl temporarily sets the variables $a and $b from the calling package with the values to be compared. The comparison function is expected to decide an ordering for $a and $b and to return an appropriate value.

A problem arises when the calling package is not the same as the package that defines the comparison function.

package X2;
sort by_42 {
    ($b eq '42') <=> ($a eq '42)   ||  $a <=> $b
}

@y = (17, 19, 42, 83, 47);
@yy = sort X2::by_42 @y;

package X1;
@x = (17, 19, 42, 83, 47);
@xx = sort X2::by_42 @x;

The first sort call will succeed (returning the values in the order 42,17,18,47,83) but the second sort call will fail. This is because the by_42 function, declared in package X2, is implictly operating on the package variables $X2::a and $X2::b, and the sort call from package X1 is setting the package variables $X1::a and $X1::b instead.

One relatively common place this problem arises is in inheritance heirarchies, where it may be cumbersome to use a comparison function in a superclass from a subclass.

THE SOLUTION

The Foreign::Sort package defines the subroutine attribute Foreign that can be applied to comparison functions. A comparison function with the Foreign attribution will perform its comparison on the $a and $b values from the calling package, not (necessarily) the package where the comparison function is defined. This allows you to define a comparison function that other users may call from other packages, and save them the trouble of setting $a and $b in the right package.

package X2;
use Foreign::Sort;
sub by_42 : Foreign {
    ($b eq '42') <=> ($a eq '42)   ||  $a <=> $b
}

package X1;
@x = (17, 19, 42, 83, 47);
@xx = sort X2::by_42 @x;

In this case, the call succeeds because the Foreign::Sort package was copying the values from $X1::a and $X1::b to $X2::a and $X2::b with each call to the X2::by_42 function.

This module was inspired by a discussion at https://stackoverflow.com/q/54842607.

LIMITATIONS

All testing for initial release done on Perl v5.22 and better. Future versions will attempt to make this module compatible with older Perls, if necessary.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Foreign::Sort

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright (c) 2019, Marty O'Brien.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available.

See http://dev.perl.org/licenses/ for more information.