Perl日記

PerlとかRubyとかPHPとかPythonとか

Mo.pmを読んだ

こんばんは。
面白そうだったのでMoを読んでみた。
http://search.cpan.org/~ingy/Mo-0.30/lib/Mo.pod

Moとは

Moose is huge. Moose led to Mouse led to Moo led to Mo. M is nothing.
Mo is more. Not much.

Moose、Mouse、Mooからできたlessなやつ。
VERSIONは0.30だった。

コード

Mo.pm
package Mo;
$VERSION='0.30';
no warnings;my$M=__PACKAGE__.::;*{$M.Object::new}=sub{bless{@_[1..$#_]},$_[0]};*{$M.import}=sub{import warnings;$^H|=1538;my($P,%e,%o)=caller.::;shift;eval"no Mo::$_",&{$M.$_.::e}($P,\%e,\%o,\@_)for@_;return if$e{M};%e=(extends,sub{eval"no $_[0]()";@{$P.ISA}=$_[0]},has,sub{my$n=shift;my$m=sub{$#_?$_[0]{$n}=$_[1]:$_[0]{$n}};$m=$o{$_}->($m,$n,@_)for sort keys%o;*{$P.$n}=$m},%e,);*{$P.$_}=$e{$_}for keys%e;@{$P.ISA}=$M.Object};

詰めてるなー。golfでもやってるのか。457byte。
ばらしてみる。

$ perl -MO=Deparse perl5/perlbrew/perls/perl-5.14.2/lib/site_perl/5.14.2/Mo.pm
package Mo;
$VERSION = '0.30';

# いきなりnoで読み込む
no warnings;

# 「Mo::」まで作る
my $M = 'Mo' . '::';

# Mo::Object空間にnewメソッドを作る
*{$M . 'Object::new';} = sub {
    bless {@_[1 .. $#_]}, $_[0];
}
;

# Mo::import
*{$M . 'import';} = sub {

    # warningsプラグマをimport
    'warnings'->import;

    # 出た。bitをいじってstrictをimport、だと思う。1538は0b11000000010
    $^H |= 1538;

    # callerの返り値を左辺が受け取っている、のではなくて
    # callerをスカラコンテキストで評価、つまり呼び出し元のpackage名と
    # '::'を結合したのを$Pに入れて、あとのハッシュはただの変数宣言。なにこれ怖い。
    my($P, %e, %o) = caller . '::';

    # @_の第一引数のクラス名を捨てる
    shift();

    # 拡張にあたる機能をなぜかnoで読み込んで、
    # eサブルーチンをここの変数のリファレンス渡して呼ぶ
    eval "no Mo::$_", &{$M . $_ . '::e';}($P, \%e, \%o, \@_) foreach (@_);

    # $e{M}があったらreturn。$e{M}はいつできる?
    return if $e{'M'};

    # %eにextendsメソッドとhasメソッドを代入
    (%e) = ('extends', sub {

        # 相変わらずのnoで読み込んで
        eval "no $_[0]()";

        # @ISAに直接叩きこんで継承
        @{$P . 'ISA';} = $_[0];
    }

    , 'has', sub {
        my $n = shift();
        my $m = sub {

            # 引数があればセッタ、なければゲッタ
            $#_ ? $_[0]{$n} = $_[1] : $_[0]{$n};
        }
        ;

        # defaultとかrequiredとかあったらその分を追加
        $m = $o{$_}($m, $n, @_) foreach (sort keys %o);

        # シンボルテーブルで呼び出し元へインポート
        *{$P . $n;} = $m;
    }
    , %e);

    # hasとextends(とその他)をシンボルテーブルで呼び出し元へインポート
    *{$P . $_;} = $e{$_} foreach (keys %e);

    # このMo.pmをuseしたものは無理矢理Mo::Objectの子供にする
    @{$P . 'ISA';} = $M . 'Object';
}
;
perl5/perlbrew/perls/perl-5.14.2/lib/site_perl/5.14.2/Mo.pm syntax OK

ほむほむ。これだけだとextendsとhasだけがエクスポートされてくるみたい。
他のrequiredなどを使うときには、podにあるように

use Mo qw'build default builder coerce is required';

みたいにする。


知ったこと。

noで読み込む

いきなり

no warnings;

しているけど、その前にuse warnings;しているわけではない。
つまり先のそれを打ち消したいわけではない。
importメソッドを作っているところの

'warnings'->import;

への布石として使っている。
そもそもno MODULE;の役割って

BEGIN {
    require MODULE;
    MODULE->unimport;
}

だから読み込みもしているんだよね。
さらにUnquoted stringな警告の出力も止めて一石二鳥みたいな感じか。
他には

eval "no Mo::$_"
eval "no $_[0]()";

が出てくるが、こっちは純粋にrequireとかuseでいいと思うんだけど、noが最小の2文字だからnoにしているのだろうか?
よくできてんなー。