パッケージ越しのソートサブルーチンがうまく働かない

Perlのソートサブルーチン(ソート定義サブルーチン)が次の「Case C: パッケージ越しのソートサブルーチンの利用」でうまく働いてくれません。

ソース

#!/usr/bin/perl
use strict;

my $by_num = sub{
    $a <=> $b;
};

############################################
# Case A: 通常のソートサブルーチンの利用
print 'Case A: ', join(', ', sort $by_num (3, 6, 2, 4, 5)), "\n";

############################################
# Case B: サブルーチンを介したソートサブルーチンの利用
sub sort_test_b{
    my $by_num = shift;
    print 'Case B: ', join(', ', sort $by_num (3, 6, 2, 4, 5)), "\n";
};

&sort_test_b($by_num);

############################################
# Case C: パッケージ越しのソートサブルーチンの利用
{
    package SortTest;

    sub sort_test_c{
        my $by_num = shift;
        print 'Case C: ', join(', ', sort $by_num (3, 6, 2, 4, 5)), "\n";
    };
}

SortTest::sort_test_c($by_num);

実行結果

Case A: 2, 3, 4, 5, 6
Case B: 2, 3, 4, 5, 6
Case C: 3, 6, 2, 4, 5

考察

$by_numのサブルーチンの中にprintを入れて検証してみましたが、どうやらCase Cでは$a、$bがundefになっているようです。なにかヒントはないかと思い、『初めてのPerl』を読み直してみました。

本当のことを話せば、sortの呼び出しの回りを取り囲んだプライベートなブロックの中で、Perlがlocal($a, $b)を実行したかのようになります。なぜなら、これらの変数は、実際にはレキシカル変数ではなく、グローバル変数だからです。(中略)ソートサブルーチンの中では、どこから来たかは気にせずに、素直に$aと$bを使いましょう。またさらに付け加えるならば、もしスコープのどこかにレキシカル変数$aまたは$bがあると、このサブルーチン定義はうまく動作しません。

初めてのPerl 第3版 p.259

どうやら、$a, $bはグローバル変数として動作しているようなので、パッケージを越えてしまうと正常に動作しないようです。

対策

グローバル変数ならば、下記のようにやればうまくいくのではないかと思い試してみました。

my $by_num_c = sub{
    $SortTest::a <=> $SortTest::b;
};

SortTest::sort_test_c($by_num_c);

結果は次の通り。

Case C: 2, 3, 4, 5, 6

見事にソートされています。やはり、$a, $bがパッケージに属しているためにうまく動作しなかったようです。

ただ、パッケージプレフィックスを指定するとうまく動くとはいえ、これではソートサブルーチンの汎用性を著しく損ねているため、対策と言えるかはわかりません。

まとめ

ソートサブルーチンの$a, $bはグローバル変数として扱われているため、パッケージ越しにソートサブルーチンを使うときには$a, $bという書き方では想定どおりに動作しないようです。ソートが実行されるパッケージのプレフィックスを指定して$a, $bを記述すれば動作するようです(ただし、パッケージごとにサブルーチンを書き換えないといけないため、汎用性が低くなります)。

より良い対策をご存知の方はいらっしゃいませんか?