読者です 読者をやめる 読者になる 読者になる

はちゅにっき

こっちのブログはまったり更新

Mouse の Destructor がよくわかっていない。。。 (DESTROY とか DEMOLISH)

ちょっとデストラクタで遊んでみた。
ら、Mouse の挙動ではまった。

Moose / Mouse を利用するとコンストラクタ *1 "new" と、デストラクタ "DESTROY" を定義することができません。
そこで、"new" の代わりに "BUILD"、"DESTROY" の代わりに "DEMOLISH" を使います。

だいたいこんな感じ *2。というのが自分の認識ですが。。。

Non Moose/Mouse なモジュールを継承して Moose/Mouse なモジュールにしたい (第二版)
http://d.hatena.ne.jp/magicalhat/20090709/1247145570

Moose / Mouse なモジュールを Moose / Mouse で継承するには、make_immmutable する際に、 inline_constructor に 0 を渡すことで、Moose / Mouse が自動でコンストラクタメソッド "new" を生成することを防ぎ、継承元のクラスのコンストラクタを利用できる。

と結論づけましたが、それと同じ要領で

__PACKAGE__->meta->make_immutable( inline_destructor => 0);

とすると、当然自分で DESTROY を定義すれば、それが呼び出せるんじゃないか?
と、ある程度確証をもっていたので、ちょっとためしてみました。

こーんなコードを書いて

use strict;
use warnings;
use ModA;
use ModB;

my $a = ModA->new;
my $b = ModB->new;

対応するモジュールを作ります。
っと、ここでちょっと気になったので実験。
一つ目のモジュール (ModA) では、Moose / Mouse にデストラクタを自動生成してもらい、自分が実行したいデストラクタの処理を DEMOLISH を使って実装します。
で、もう一つのモジュール (ModB) では make_immutable の際に inline_destractor => 0 を指定し、DESTROY メソッドを自前で実装してみます。
↓こんな感じ。

  • ModA Moose / Mouse によって提供される DEMOLISH を利用する
package ModA;

use strict;
use warnings;
use feature 'say';

use Any::Moose;
__PACKAGE__->meta->make_immutable;
no Any::Moose;

sub DEMOLISH
{
    say "DESTROY: A";
}
  • ModB inline_destractor => 0 を指定して自分で DESTROY を実装する
package ModB;

use strict;
use warnings;
use feature 'say';

use Any::Moose;
__PACKAGE__->meta->make_immutable( inline_destructor => 0 );
no Any::Moose;

sub DESTROY
{
    say "DESTROY: B";
}

さてさて、どーなる?
というわけで、Moose で実行した場合。。。

$ env ANY_MOOSE='Moose' perl test.pl
DESTROY: A
DESTROY: B

期待通りの結果が得られました。
わーい。
DESTROY される順番に関しては「リファレンスカウンタがうんぬん。。。」があるので、実際のプログラムではこんなきれいな順番で実行されるとは思いませんが、とりあえずデストラクタを定義できたってゆーことですね。
次、Mouse。

$ env ANY_MOOSE='Mouse' perl test.pl
Name "Mouse::Object::DEMOLISH" used only once: possible typo at /usr/local/share/perl/5.10.0/Mouse/Meta/Method/Destructor.pm line 13.
DESTROY: A
DESTROY: B

おっ、おっ、おっ?
「Mouse::Object::DEMOLISH って1回しか使われてない (サブルーチンの定義だけがある) んだけどさ Typo してない?」
って警告がでたよ!
あれ?そもそも Mouse::Object::DEMOLISH がいつの間にか定義された?
結果は期待通りで、エラーは出てないけれど、警告出ているのは気持ちわるいなぁ。。。
というわけで、警告が出ているソースを眺めてみよぅ。

package Mouse::Meta::Method::Destructor;
use strict;
use warnings;

# make_immutable で
# inline_destractor => 1  (明示的に 0 を指定しない限り 1 が標準)
# を指定すると飛んでくる
sub generate_destructor_method_inline {
    my ($class, $meta) = @_;

    my $demolishall = do {
        if ($meta->name->can('DEMOLISH')) {
            my @code = ();
            no strict 'refs';
            for my $klass ($meta->linearized_isa) {
                if (*{$klass . '::DEMOLISH'}{CODE}) {
                    push @code, "${klass}::DEMOLISH(\$self);";
                }
            }
            join "\n", @code;
        } else {
            return sub { }; # no demolish =)
        }
    };

    my $code = <<"...";
    sub {
        my \$self = shift;
        $demolishall;
    }
...

    local $@;
    my $res = eval $code;
    die $@ if $@;
    return $res;
}

1;

う〜ん?
う〜ん?

if ($meta->name->can('DEMOLISH'))

あたりが怪しいんですかね?
何かのタイミングで Mouse::Object が DEMOLISH_ABLE*3になっているようで。
それともそもそも「でもりっしゃぶる」な状態が正しいのか。。。?
ちょっとまだソースをぱっと見ただけなのでまったく分かっていないんですが、そもそも DESTROY と DEMOLISH が混在するようなケースを作り上げるのが間違っているのか、それとも。。。?
あと、まだ試してないんだけどコンストラクタが混在している場合も同じようにダメなのかな?

という辺りをもう少し、しっかりと調べてみたいなーと思います。
とにかく、今回の結論は
inline_destractor => 0 を指定すれば、自分で定義する DESTROY が呼び出せるってゆーことで!


わーい。

*1:と暗黙の了解となっている

*2:BUILDARGS とかもあるけれど

*3:命名: でもりっしゃぶる