2009年11月アーカイブ

Perlでクラス(モジュール)をアンロードする方法として、Class::Unloadを使っています。

Class::Unload->unload('Some::Unneeded::Class');

当初、木本さん(perlcodesampleさん)による「クラスをアンロードする方法」の記事や、そこで言及されているClass::MOP::Class::DESTROY()を参考にして、以下のようなアンロードメソッドも書いてみました。その後、用途に適合するClass::UnloadモジュールがCPANで公開されていることが分かったので、自分の場合はこれを活用させていただこうと思った次第です。

いつの日かClass::Unloadがどうしても使えない場面に立ち会ってしまったら、これを引っ張り出してこようと思います。「だが、私はCPANを使えない!」の全ての処方箋が通用しないという、あまりぞっとしない場面ですが......。

sub _unload_class {
    my ($self, $class) = @_;

    my @route_to_class = split '::', $class;

    # unload class in target library which is dump target of generated classes
    # cf. http://d.hatena.ne.jp/perlcodesample/20091101/1246274997
    # below codes stolen from Class::MOP::Class::DESTROY()
    {
        no strict 'refs';

        # delete inheritance information from @ISA
        @{
            $class . '::ISA'
        } = ();

        # delete class symbol from symbol table
        %{
            $class . '::'
        } = ();

        # delete class symbol from upper namespace
        delete ${
            join '::', @route_to_class[0 .. $#route_to_class - 1], q{}
        }{
            $route_to_class[-1] . '::'
        };

        # delete loading information
        delete $INC{
            (join '/', @route_to_class ) . '.pm'
        };
    }

    return;
}

なお、上記はとあるクラスのメソッドとして実装していますが、$selfは使っていないので、単なるサブルーチンにも出来ます。

ところで、普通に暮らしているだけではクラスをアンロードしたいと思う場合が殆ど全くありません。アンロードしたいという稀少な事例の一つとして、拙作のDBICx::Modeler::Generatorでクラスをリロードするという例を掲げておきます(該当箇所はfcf4b0de...コミットに於けるDBICx::Modeler::Generator::Classの147行目です)。

このモジュールについては稿を改めてご紹介するつもりです。

Perlでtrim(さらに完全版)

| コメント(0)

perl で trim(完全版)」や「ハッシュ変数に存在しないキーを指定した場合の値は何?」という記事をはてブで拝見しました。Perlでいかにtrimする(前後の空白を取り除く)か、という命題です。しかし、残念なことに、文字コードによってはこれでもまだ完全ではありません。

  • モダンなPerlでは、プログラム内部では文字列の文字コードをUTF-8で扱い、必要に応じて入力時にデコード/出力時にエンコードする方法が一般的です。
  • こうした常套句については、コードスニペットをコピペするより、CPANモジュールを出来るだけ使うと好ましいです。理由は「かなり使えるPerl正規表現のまとめ」の通り。今回の場合、String::Utiltrim()が使えます。

ということで、モダンにtrimする場合には、以下のように書けます。

use strict;
use warnings;

use utf8;

use String::Util qw(trim);
use Test::More;

# half-width space : 半角スペース
is trim(' foo'),  'foo' => 'half prefix';    # 接頭スペース
is trim('bar '),  'bar' => 'half suffix';    # 接尾スペース
is trim(' baz '), 'baz' => 'half circumfix'; # 接周スペース

# full-width space : 全角スペース
is trim(' foo'),   'foo' => 'full prefix';
is trim('bar '),   'bar' => 'full suffix';
is trim(' baz '), 'baz' => 'full circumfix';

# 両対応
is trim('  foo'),    'foo' => 'both prefix';
is trim('bar  '),    'bar' => 'both suffix';
is trim('  baz  '), 'baz' => 'both circumfix';

done_testing();
__END__

use utf8しつつUTF-8でテストスクリプトを保存することで、全角スペースも(trim()の中でも使われている正規表現である)\sに該当するようになります。

実際にアプリケーションで使う場合には、Encodeを使って外から来た文字列はdecode()して、外に出す文字列は(必要に応じて)encode()することになります。出力はアプリ作者側でどうにでも出来るので、特に指定する必要がない場合にはUTF-8のままで問題ないでしょう。日本の携帯電話向けには、Shift-JISなどと適宜変えることになります。入力側はEncode::Guess辺りで日和っておくといい感じです。

勿論、モダンな環境を使えない場合や、CPANモジュールが使えない場合(実際は「だが、私はCPANを使えない!」の通り、あまりないです)も多々あるわけで、そうした場合にはトラックバック先に挙げられているような手法を使うのが良いと思います。他にも、「PerlでTrimする!」という記事もあります。

Mooseクラスでのクラス定数は、MooseX::ClassAttributeで定義する方法もありますが、素直なのはクラスメソッドとして実装してしまうことでしょう。これならば、ロールとの相性も良いです。

しかし、クラス定数(のように使うクラスメソッドの戻り値)が単純な値であればともかく、何かの値を元に計算して導出するような代物だった場合、クラス定数(のように......以下略)を使う度に計算されたのではたまりません。

こうした場合には、Memoizeによるメモ化(memoization)が定番の処方箋となります。また、(もはやメモ化の文脈での例としては定番である)フィボナッチ数の計算など、クラス定数として使うものでないメソッドについても、同様にメモ化は強力な最適化方法論です。

Mooseクラスでは単純にMemoizeモジュールを使えば良いのですが、Mooseロールでは少々込み入った手順で使う必要があることが分かったので、備忘録的に書いておきます。

Mooseクラスの生成時に、コンストラクター引数として与えられたハッシュまたはハッシュリファレンスに着目して処理を行いたい場合があります。

例えば、初期化ハッシュキーを跨いだ検証などが挙げられます。triggerの使い方についての記事で説明用に作った、元号と西暦のアトリビュートを持つ昭和クラスで想定してみます。

元号と西暦の両方を指定された場合、その両方のアトリビュートが未指定になります。undefという値が入っているのではなく、has_imperial_eraなどのpredicateメソッドの戻り値が偽であるという意味です。

それでは困るので、コンストラクター引数に指定するのは元号と西暦のどちらか一方のみに制限する......という要件が出て来たこととします。その場合、ハッシュとハッシュリファレンスの両方でキーを見付ける処理をBUILDARGSに書くのではなく、親クラス(最終的な親はMoose::Object)のBUILDARGSを呼ぶようにすると、素直に書けます。

# ...

around BUILDARGS => sub {
    my $next  = shift;
    my $class = shift;
    # ここでは@_はハッシュかも知れないしハッシュリファレンスかも知れない

    # 親クラス(Moose::Object)のハッシュリファレンス化はBUILDARGSに任せる
    my $init_args = $class->$next(@_);  # ここではハッシュリファレンス

    confess 'Initialization argument must be '
          . 'any one of imperial_era or christian_era'
            if exists $init_args->{imperial_era} 
            && exists $init_args->{christian_era};

    return $init_args;
};

# ...

こんなものは常識なのでしょうけれども、意外とこういう初歩的なところの認識が甘くて、今までは(後述するように)ハッシュリファレンスのみを食べるような偏屈APIを撒き散らしていました。反省反省。

以下は補足情報です。

筆者"Gardejo"について

  • Twitter: @gardejo
  • GitHub: gardejo
  • CodeRepos: gardejo
  • CPAN: MORIYA

このサイトについて

Eorzea System Worksは、架空のシステム開発結社です。

FF14.name (FinalFantasyXIV.name)では、アヴァター(プレイヤーキャラクター)の管理システムやイベント出欠・リマインダシステムや、リンクシェル(LS)運営・管理システムやDKPシステムなどを設計・開発・公開する予定です。

関連サイト

関連サイトでは、他にもFF14に関連するサイトをいくつか紹介しています。

リンク, トラックバック歓迎

このブログへのリンク(どのページでも構いません)やトラックバックを歓迎します。

設計・開発・運用の参考にさせていただきますので、コメントもお気軽にお寄せください!

個別の記事に対するご意見などのほか、目安箱もご用意しています。

このアーカイブについて

このページには、2009年11月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2009年10月です。

次のアーカイブは2009年12月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

最近のコメント

2014年2月

            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28  

やや真面目なサイト