self-0.11なるものを触ってみた

ぼへーっとCPANRSS眺めてたらselfとかいうのが目に付いた。

selfというネーミングから想像するに、なにやら面白いことしてくれそうな匂いがぷんぷん香ってきたので触ってみたわけです。

するとまさしくほぼ想像通りのことをやってくれるモジュールだったのですよ。

以下概要まんまだけども例をば。

 package Hoge;
 
 use strict;
 use warnings;
 use self;
 
 sub new {
     my $class = shift;
     return bless {} , $class || ref $class;
 }
 
 sub hoge {
     if ( args ) {
         self->{hoge} = args;
     }
     return self->{hoge};
 }
 
 
 my $obj = Hoge->new;
 $obj->hoge(100);
 
 print $obj->hoge; # 100

へー、面白いですね。

「my $self = shift」をしなくてもselfから直接オブジェクトにアクセスできます。

で、引数はargsからアクセスできるようになるといった感じです。

しかしselfに引数(@_)を委譲してないのにどうやって実現してるのか気になったのでソースを覗いて見ました。

以下コード。

use strict;
use warnings;

package self;
use base 'Exporter::Lite';
use v5.8.0;

our $VERSION = '0.11';

our @EXPORT = qw(self args);

sub _args {
    my @c = do {
        package DB;
        @DB::args = ();
        caller(2);
    };
    return @DB::args;
}

sub self {
    (_args)[0];
}

sub args {
    my @a = _args;
    return @a[1..$#a];
}

1;

思ったよりも短くてびっくらちょ。てっきりXSかと思ったけどPurePerlだった。

なるほどなるほど、@DB::argsを利用しているわけですね、こんなのあったことを失念してました。

caller関数は特殊な機能があって、DBパッケージ内でcallerを呼ぶと@DB::argsにcallerで指定した階層に渡された引数が格納されるのです。このテクニックはCarpモジュールとかでも使われています。

caller(2)と書いてるので「_args」の前の「self」の前の「元メソッド」に渡された引数が格納されるということになり、結果selfに引数を渡さなくても元メソッドのオブジェクトが取れるという感じですね。

便利といえば便利なので使ってみるのもアリかなーと思ったのですが、やっぱり気になるのは速度ですよね。

ってことでベンチマークをとったりなんかしたり。

use Benchmark qw(cmpthese timethese :hireswallclock);

package SelfClass;
use self;
sub new { bless {} , shift }
sub method {
    if ( args ) {
        self->{data} = (args)[0];
    }
    return self->{data};
}

package SelfNormal;
sub new { bless {} , shift }
sub method {
    my ($self,$args) = @_;
    if ( $args ) {
        $self->{data} = $args;
    }
    return $self->{data};
}

package main;
sub self_class {
    my $obj = SelfClass->new;
    $obj->method(100);
}

sub self_normal {
    my $obj = SelfNormal->new;
    $obj->method(100);
}

cmpthese(1000, {
    'self_class'  => \&self_class,
    'self_normal' => \&self_normal,
});

実行結果:

                             Rate        self_class       self_normal
 self_class               15873/s                --             -100%
 self_normal 999999999999999870/s 6299999999999999%                --

なんとまあ悲惨な結果になりました。他かだか1000ループでコレではちょっと使い物にならないかなぁ・・・。

考え方としては面白いし、見た目も結構好きなのでなんかちっちゃなツール作る時とかに無理やり導入してみるのも有りか・・・な?