rand の返り値を固定してあげて、テストしたい時などに行う
Perl の built-in 関数の上書きについて書いてみます
前置き
まずは簡単な上書きの例を紹介してみます
重み付け抽選をしてくれる Data::WeightedRoundRobin を使うコードのテストを見てみます
次のコードは、rand
を 1 で固定しているので、常に抽選結果はbar
になります
BEGIN {
*CORE::GLOBAL::rand = sub { 1 }
}
use Test::More;
use Data::WeightedRoundRobin;
my $dwr = Data::WeightedRoundRobin->new([qw/foo bar/]);
is $dwr->next, 'bar' for 0..10; # 何回回したって平気..!!
done_testing;
perl ./test.pl
# => ok
ここで問題提起です
こんな感じの Preload モジュールを読み込んで、このテストを実行した場合どうなるでしょうか?
package Preload;
use Data::WeightedRoundRobin;
1;
おそらく、テストはfail すると思います
perl -MPreload ./test.pl
# => fail!!
前置きが長くなってしまいましたが、
このfailについてと回避策について書いてみます
仕様確認
早速、仕様 を確認してみます
To override a built-in globally (that is, in all namespaces), you need to import your function into the CORE::GLOBAL pseudo-namespace at compile time:
BEGIN {
*CORE::GLOBAL::hex = sub {
# ... your code here
};
}
超訳すると...
「どこでも上書きしたbuilt-in を使いたいなら、
built-in 関数が CORE::GLOBAL という名前空間に生えていることにするから、
それをコンパイル時に上書きするようにして」
という感じでしょうか
普段 ユーザ定義の関数の場合、次のように *Foo::hello
を上書きします
built-in の場合は、CORE::rand
のように CORE
に生えているのですが、
上書きする場合は、CORE::rand
ではなく、CORE::GLOBAL
を書き換えるようにして!
というのが、built-in ならではの1つの制約になります
package Foo {
sub hello { 'hello!!' }
}
warn Foo::hello; # => hello!!
*Foo::hello = sub { 'fufufu' };
warn Foo::hello; # => fufufu
また、もう一つの制約が、コンパイル時 に上書きすることです
例えば、上記のユーザ定義の関数と同様に上書きしてみても、
これは、意図通り上書きされません
package Foo {
sub hello { rand }
}
warn Foo::hello;
BEGIN {
*CORE::GLOBAL::rand = sub { 1 };
}
warn Foo::hello; # => is NOT 1
前置きの解説
このコンパイルの制約を踏まえると、前置きのfailは理解できます
つまり、前置きのfailは、次のようなコード解釈がなされ、
rand のコンパイル前に、上書きができなかった為に、コンパイルの制約にそぐわなかったのです
use Data::WeightedRoundRobin; # Preload
BEGIN {
*CORE::GLOBAL::rand = sub { 1 }
}
use Test::More;
use Data::WeightedRoundRobin;
my $dwr = Data::WeightedRoundRobin->new([qw/foo bar/]);
is $dwr->next, 'bar' for 0..10; # 何回回したって平気..!!
done_testing;
perl ./test.pl
# => fail
このテストを通すには?
Preload で rand が 呼び出されている場合はどうするか、やり方を考えてみます
次の例のように、何よりも先にbuilt-in を上書きしておけば、どこでも上書きされたものを利用でき、
その上、上書き後は好きなタイミングで上書きが適用できることを利用してみます
BEGIN {
*CORE::GLOBAL::rand = sub { 1 };
}
package Foo {
sub random { rand }
}
BEGIN {
*CORE::GLOBAL::rand = sub { 2 };
}
warn Foo::random; # => is 2
結論、次のように、
Preload
の先頭で、rand
を上書きしてみます:)
package Preload;
BEGIN {
*CORE::GLOBAL::rand = sub { CORE::rand($_[0]) }
}
use Data::WeightedRoundRobin;
1;
perl -MPreload ./test.pl
# => ok!!
やりましたね!
まとめ
- Perl で built-in を上書きは、CORE::GLOBAL に生えているもんだとして、コンパイル時に上書きする
よりスマートな方法があれば、教えていただけると嬉しいです..!
以上です!