モジュールがMoose依存してた。別れたい…

さて毎年年末が近づくとブログ更新頻度が下がるid:fbisですが、Mooseも馴染んできたところなのでそろそろMooseに対して一言いっておくかということでね、はい。

Mooseは素晴らしい。とてもベリー素晴らしい。何より人に優しい。人間に優しい。

がっ!しかし、Mooseはまだ使うには早すぎた。ナウシカ巨神兵ばりに早すぎたんだ。

実際に組み込んでみて感じた問題点。

大きく分けて二つ。



その壱:オブジェクト生成のコスト

やはりなんといってもnewのコストが高い。遅い。

一つ一つを見ればそこそこなんだけどやはりnewのコストは高い。

Mooseを使うのであれば極力newを避けるような仕組みの中に取り入れないと微妙。Catalystで例えるならsetupの時点でnewして以後キャッシュされるような実装だ。

newさえ終わればあとは(複雑なMooseの使い方をしてない限り)それほど問題ではないように思える。

ちょっとした小さなモジュールを作るのにMooseは凄い便利。タイプ量も物凄い減るし簡素で可読性も良い。

だからなおさら小さなモジュールでもMooseを使いたくなる。するとnewの呼ばれる回数が多くなる。どんどんコストが上がっていくという寸法だ。

ぶっちゃけMooseで個人的に良く使う機能と言えばhasのdefault&lazyやtrigger、coerceだったりする。

ある意味これだけのためにMooseを使っていると言っても過言ではない。

例を示そう。以下のようなMooseで書かれた定義があるとする。

has foo => (
    is      => 'rw',
    lazy    => 1,
    default => sub {
        my $self = shift;
        return 'default';
    },
    trigger => sub {
        my $self = shift;
        $self->{foo} .= ' and trigger';
    }
);

これは初めてfooメソッドがばれたときに'default'という文字列を格納、キャッシュする処理だ。

また、fooメソッドに引数が与えられた場合にのみ既存の値に' and trigger'という文字列をくっつける処理でもある。

このような処理をPOPOで実装すると以下のようになるだろう。

sub new {
    my $proto = shift;
    my %param = ref $_[0] eq 'HASH' ? %{+shift} : @_;
    my $self  = bless \%param , ref $proto || $proto;
    
    for my $member ( qw/foo/ ) {
        $self->$member($self->{$member}) if exists $self->{$member};
    }
    
    $self;
}

sub foo {
    my $self = shift;
    
    # trigger
    if ( @_ ) {
        $self->{foo} = shift;
        $self->{foo} .= ' and trigger';
        return $self->{foo};
    }
    
    # default
    exists $self->{foo} or $self->{foo} = sub {
        return 'default';
    }->();
    
    return $self->{foo};
}

これはひどい(と思うよね?)

一体「foo」と何度書いたら気が済むんだ。いつかコピペミスやタイプミスをするだろう。

そしてnewメソッド。ここでtriggerのために専用の処理を突っ込まないといけない。barメソッドを増やした場合はここも更新しないといけないというわけだ。

このようにプレーンな状態ではtriggerやdefaultを逆に書いてしまったり、間の空間に出来心で何かしらの処理を書いてしまったりしてどんどん始めの実装者の思想が失われていき、混沌に陥るわけだ。

triggerやdefaultがきちんとわかれているMooseではそのような事態は起こらない。

また、この例では省略しているがcoerceが入ってくるともうぐちゃぐちゃになるだろう。

小さなモジュールを作る分にはこの機能だけでもあれば物凄い作業の効率化が図れると思う。しかしこの機能を使うためだけにMooseを使うにはまだ少しコストが高すぎるという結論なんだよね。



その弐:依存モジュールの敷居の高さ

Mooseは依存度が高い。まぁこれに関しては実はうちの環境ではあまり問題はない。自社でいろいろいじれる環境であれば多少依存度が高くてもごりごりインストールすればいいのだから。

しかし昨今のMENTA等の軽量フレームワークの流れの中でMoose依存を外しましたなんていう流れもあるのでそうも言ってられない。

汎用的なCPAN向きのモジュールを作るのにもMooseは適している。Class::MOPを基盤としたパッケージ管理やMoose::Role等を使ったプラグインの構築などなど。非常に作りやすい。

モジュール提供側も提供しやすいし、プラグイン作成者にも作成しやすい環境作りができる。

が、やはりその依存の高さゆえになかなか広まりにくいのも事実。

そういう環境を用意できる人にしか届かないのはあまりにも不憫。だからこその軽量フレームワークという流れもきてるんだろうとは思うけど、その流れにMooseが乗るのはまだまだ厳しいだろう。








・・・・・・・・・・という文章を書いてる途中にShikaとかいうものがあるのを知ったのでちょいとそれも調べてみたところ、Mooseのメモリ食い過ぎ!&起動時間遅すぎ!を改善したものみたい。

ただ実行速度に関しては軽くベンチったところやはりMooseの方が倍以上高速(追記:ただしXS有効にしたShikaであればMooseよりも格段に早い。詳細はこちら)なので、そういった意味では前者がボトルネックになる素のCGIな環境でMoose的な構文を使いたい場合にShikaは良さげな感じがする。

とりあえず個人的にはRoleさえも必要なくてアクセサ生成とdefault,lazy,trigger,coerce,requiredがあれば日々の小さなモジュール作成が大いに作業効率化されるのでそれに特化したモジュールでも作ってみようかしらと思う今日この頃。

しかし、Moose、Mouse、Shikaなどどんどんシンプルになっていってる流れを見ると非常におもしろい。こうやって洗練されていくのかもね。

追記

Benchmarkコード張れ( ゚Д゚)ゴルァ!!と総ツッコミを受けて追記
話の主眼じゃなかったので必要ないかな?と思ったんだけどやっぱいるよねー。

さっきの記事のShikaのベンチのコード - Unknown::Programming