8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

rand の返り値を固定してあげて、テストしたい時などに行う
Perl の built-in 関数の上書きについて書いてみます

前置き

まずは簡単な上書きの例を紹介してみます
重み付け抽選をしてくれる Data::WeightedRoundRobin を使うコードのテストを見てみます
次のコードは、rand を 1 で固定しているので、常に抽選結果はbar になります

test.pl
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 モジュールを読み込んで、このテストを実行した場合どうなるでしょうか?

Preload.pm
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 のコンパイル前に、上書きができなかった為に、コンパイルの制約にそぐわなかったのです

test.pl
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 を上書きしてみます:)

Preload.pm
package Preload;

BEGIN {
    *CORE::GLOBAL::rand = sub { CORE::rand($_[0]) }
}

use Data::WeightedRoundRobin;

1;
perl -MPreload ./test.pl
# => ok!!

やりましたね!

まとめ

  • Perl で built-in を上書きは、CORE::GLOBAL に生えているもんだとして、コンパイル時に上書きする

よりスマートな方法があれば、教えていただけると嬉しいです..!
以上です!

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?