Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a sorting subroutine from another package

I have a script and a package like so:

# file: sortscript.pl
use strict;
use warnings;
use SortPackage;

my @arrays = ([1,"array1"],[10,"array3"],[4,"array2"]);

print "Using sort outside package\n";
foreach (sort SortPackage::simplesort @arrays){
    print $_->[1],"\n";
}

print "\nUsing sort in same package\n";
SortPackage::sort_from_same_package(@arrays);

--

# file: SortPackage.pm
use strict;
use warnings;
package SortPackage;

sub simplesort{
    return ($a->[0] <=> $b->[0]);
}

sub sort_from_same_package{
    my @arrs = @_;
    foreach (sort simplesort @arrs){
        print $_->[1],"\n";
    } 
}
1;

Running the script produces the output:

$ perl sortscript.pl
Using sort outside package
Use of uninitialized value in numeric comparison (<=>) at SortPackage.pm line 15.
Use of uninitialized value in numeric comparison (<=>) at SortPackage.pm line 15.
Use of uninitialized value in numeric comparison (<=>) at SortPackage.pm line 15.
Use of uninitialized value in numeric comparison (<=>) at SortPackage.pm line 15.
Use of uninitialized value in numeric comparison (<=>) at SortPackage.pm line 15.
Use of uninitialized value in numeric comparison (<=>) at SortPackage.pm line 15.
array1
array3
array2

Using sort in same package
array1
array2
array3

Why am I not able to correctly use the subroutine to sort with when it is in another package?

like image 723
MattLBeck Avatar asked Jul 16 '12 16:07

MattLBeck


3 Answers

As has been mentioned, $a and $b are package globals, so another solution is to temporarily alias the globals at the call site to the ones in package SortPackage:

{
    local (*a, *b) = (*SortPackage::a, *SortPackage::b);
    foreach (sort SortPackage::simplesort @arrays){
        print $_->[1],"\n";
    }
}

But this is pretty ugly, of course. I would just have SortPackage export a complete sorting routine, not just a comparator:

package SortPackage;
use strict;

sub _sort_by_first_element_comparator {
    return $a->[0] <=> $b->[0];
}

sub sort_by_first_element {
    return sort _sort_by_first_element_comparator @_;
}
like image 105
Sean Avatar answered Nov 08 '22 16:11

Sean


The special variables $a and $b are package globals. Your subroutine expects $SortPackage::a and $SortPackage::b. When you call it from sortscript.pl, the variables $main::a and $main::b are being set by sort.

The solution is to use a prototyped subroutine:

package SortPackage;

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

It's a little slower (since you have actual parameters being passed, instead of reading pre-set globals), but it allows you to use subroutines from other packages by name, as you are attempting.

like image 5
chepner Avatar answered Nov 08 '22 16:11

chepner


$a and $b are special "package global" variables.

To use the main scope's $a and $b your comparator function would have to refer to $::a or $main::a (and likewise for $b).

However that comparator function then wouldn't work when called from any other package, or even from within its own package.

See the perlvars help and the perldoc for the sort function. The solution is also in the latter help text:

If the subroutine’s prototype is "($$)", the elements to be compared are passed by reference in @_, as for a normal subroutine. This is slower than unprototyped subroutines, where the elements to be compared are passed into the subroutine as the package global variables $a and $b (see example below). Note that in the latter case, it is usually counter‐productive to declare $a and $b as lexicals.

like image 6
Alnitak Avatar answered Nov 08 '22 15:11

Alnitak