ちょっと書こうと思っていること - ぱいぱいにっきでSub::Decorator書くよとか言ってたんですけれど、お前それAttribute::HandlersとかSub::Attributeで出来るんちゃうんかということでやってみた。
package AttrStopwatch; use strict; use warnings; use utf8; use 5.014; use Sub::Attribute; use Time::HiRes qw/gettimeofday tv_interval/; sub Stopwatch :ATTR_SUB{ my ($class, $sym_ref, $code_ref, $attr_name) = @_; my $stack_num = 0; my $func_name = *$sym_ref{NAME}; no warnings qw/redefine/; *$sym_ref = sub { my $begin = [gettimeofday()]; $stack_num++; &$code_ref; $stack_num--; say $func_name.': '.tv_interval($begin, [gettimeofday()]).' sec.' if $stack_num == 0; }; } 1;
use strict; use warnings; use utf8; use 5.014; use parent qw/AttrStopwatch/; use Memoize; sub tarai :Stopwatch { my ($x, $y, $z) = @_; return $y if $x <= $y; return tarai(tarai($x-1, $y, $z), tarai($y-1, $z, $x), tarai($z-1, $x, $y)); } say 'begin tarai'; tarai(20, 10, 1); say 'end tarai'; memoize('tarai'); say 'begin tarai with memoize'; tarai(20, 10, 1); say 'end tarai with memoize';
実験には竹内関数(通称たらい回し関数)を使っている。再帰しまくるので時間がかかるという代物。Wikipediaに竹内関数はメモ化すると早くなるでっていうことなので、Memoizeモジュールでメモ化したものと比較。
あと、何の工夫もないまま関数を包むと再帰しまくるので関数呼び出しごとにwarnが走るので結局全体の実行秒数いくらやねんってなるので、再帰の深さを覚えておいて1回だけ呼ぶようにした。でも結局開始時間はその都度取ってるのはご愛嬌ということで。
で、実行結果。
$ perl sub-attribtue-test.pl begin tarai tarai: 23.658994 sec. end tarai begin tarai with memoize tarai: 0.007111 sec. end tarai with memoize
メモ化したらはええええ。当たり前だけど。でもこれって引数もっと大きくしたら今度はメモリ足らなくなるんだろうなとか。今時のコンピュータのメモリを溢れさせるにはどんぐらいの引数指定すればいいんだろ。
まあこんなかんじでデコレータっぽい感じにできました。以下つまづいた点。
- 関数名取得するのどうすんねん
属性を定義する関数の第2引数がシンボルテーブルとかいうやつで、ハッシュリファレンスとして読めるらしい。で、NAMEってキーに実行中の関数名が入っているそうな。
- え、勝手に関数実行されるんだけれど
Pythonのデコレータは関数を返す方式なんですけれど、今回の場合はそのまま実行すると勝手に関数実行されて、どうやって前後とか判定するの?とかなる。調べるとシンボルテーブルに入っているコードリファレンスを上書きすることで、関数の書き換えを行う形らしい。なのでcode redefineが出ちゃう。
まあ、なんか形になってよかった。
とはいえno warnigs使ってたりするし、多分すごく小さいモジュールになるだろうけれど、もしかしたら需要あるのかもしれないなーとか思ったり。あとMemoizeが「え、これだけで?」って感じでメモ化出来るのがカジュアルでよかった。