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

Perl日記

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

namespace::autocleanについて調べた

読んでます。

Perl CPANモジュールガイド

Perl CPANモジュールガイド


その中でずっとなんとなく使っていたnamespace::autocleanについても言及されていたので、せっかくなのでちゃんと調べてみたメモ。


namespace::autoclean - search.cpan.org

主要機能

そもそもこのモジュールどこでよく見たかって、Catalystの5.8系統でスケルトンを作成するとアプリケーションクラスとコントロールクラスにデフォルトで書かれているからだった。
(どのバージョンからこうなったんだろ?)

$ catalyst.pl Ctest
Ctest/lib/Ctest.pm
package Ctest;
use Moose;
use namespace::autoclean;

…
Ctest/lib/Ctest/Controller/Root.pm
package Ctest::Controller::Root;
use Moose;
use namespace::autoclean;

…


で、namespace::autocleanの主要な機能は、上記本やPODの最初に書かれているとおり、exportされてくるサブルーチンの使用可能スコープをそのパッケージ(クラス)に留めること。

何もしなければ、Catalystスケルトンの例でMooseが寄越してくるhas()とか、extends()とか、with()とかが、Ctest->has()として使用可能である。
そんな使い方もどうかと思うが、「可能である限りどこかで使用されてしまうリスクは消えない」。

だから使えないようにしてリスクは消す。

package Foo;
use namespace::autoclean;
use Some::Package qw/imported_function/;

sub bar { imported_function('stuff') }

# later on:
Foo->bar;               # works
Foo->imported_function; # will fail. imported_function got cleaned after compilation

ただ「Foo内で」通常の「::」でコールすることは、できてしまう。

Foo::imported_function(); # works!

(だからbar()の中で使える訳か?)


Foo.pmをコールする側では、見えない。

use Foo;

Foo->bar; # ok
Foo::imported_function(); # abort!
# Undefined subroutine &Foo::imported_function

どうやって実現させている?

ソース見るとB::Hooks::EndOfScopeon_scope_end()で「コンパイル終了後に実行」を実現しているみたい。なるほど。

その中で、Class::MOP::Classとnamespace::cleanを使って、パッケージ内のサブルーチンシンボルを消していく、という感じか。

Class::MOP::ClassはMooseインスタンス作成に使われてるってモダパーに載ってた。

ほかには?

cleanee

-cleaneeスイッチを使って、自作ライブラリの中に同様の機能を組み込める。

Hoge.pm
package Hoge;
use namespace::autoclean (); # no cleanup, just load

sub import {
  my $caller = caller; # package name
  namespace::autoclean->import(
    -cleanee => $caller,
  );
}

1;
Fuga.pm
package Fuga;
use Hoge;
use Carp qw/ carp /;

Fuga->carp; # will fail!

1;
also

-alsoによって追加でcleanするサブルーチンを指定できる。

also.tより
{
  package Foo;
  use namespace::autoclean -also => ['bar'];
  use namespace::autoclean -also => 'moo';
  sub bar {}
  sub moo {}
  sub baz {}
}

ok(!Foo->can('bar'), '-also works');
ok(!Foo->can('moo'), '-also works with string argument');
ok( Foo->can('baz'), 'method not specified in -also remains');


どちらかというと、以下のような使い方だと思う。

cleanee.tより
{
  package My::Cleaner;
  use namespace::autoclean (); 

  sub import {
    namespace::autoclean->import(
      -cleanee => scalar(caller),
      -also    => 'blast',
    );  
    *{Foo::boom} = sub { 'boom' };
  }   
}

{
  package Foo;
  BEGIN { My::Cleaner->import } # use My::Cleaner tries to load it from disk
  sub explode { 'explode' }
  sub blast   { 'blast' }
}

ok( Foo->can('explode'), 'locally defined methods still work');
ok(!Foo->can('boom'), 'imported functions removed');
ok(!Foo->can('blast'), '-also methods removed');

Fooの中でimportされずalsoの対象でもないblast()だけが使えると。すごい。

まとめ

もしパッケージ内でexport「される」サブルーチンがあるなら、素直にnamespace::autocleanしておけばよい。
もしパッケージ内でexport「する」サブルーチンがあるなら、importでnamaspace::autoclean->importして消してあげてもよい。


あとClass::MOP::Classは魔法だと思った。


参考:
はてなブログ