最近、Moose の素晴らしさに気付いて使い始めた sasata299 です。こんにちわ。(=゚ω゚)ノ

Moose ってとても便利だと思うんですが、どう使ったらいいものかよくわからない・・・。そんな人も多いのではないかと思ったので、今日は Moose を使う一例を紹介してみます。

そもそも Moose の利点って何?という話ですが、Moose にはこのような利点があります。
  • use strict; use warnings; しなくていい。自動でやってくれる
  • Perl5 で OOP をより簡単にできる
  • 型チェックしてくれるので、本質的な部分に集中できる。可読性も(慣れれば)上がる
素晴らしいじゃないですかぁ〜

ただ依存モジュールが多かったり、コンストラクタの処理に時間がかかるという欠点もあります。でもその欠点を補って余りある便利さです。

例えば、以下のような仕様でスクリプトを作る場合を考えてみましょう。

スクリプトの仕様
  • コマンドライン引数として、--fromオプション、--toオプション、--typeオプションがある
  • --fromオプション、--toオプションは必須。YYYY-MM-DD形式で指定する
  • --typeオプションは 1〜5 のいずれかの整数値をとる(指定しない場合 1 がセットされる)

コマンドライン引数の処理って値のチェックが面倒くさいですよね。。これを Moose を使わずに実現するとなると、こんな感じでしょうか?

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use DateTime;

GetOptions( my %config, 'type=i', 'from=s', 'to=s' );
$config{type} ||= 1;

# --fromオプション、--toオプションが指定されていなかったらエラー
die "not defined error" unless defined $config{from} and defined $config{to};

# 存在しない日付だったり、日付の形式がおかしかったらエラー
for ( @config{qw/from to/} ) {
    my @date = split /-/, $_;
    DateTime->new(
        year  => $date[0],
        month => $date[1],
        day   => $date[2],
    );
}

# --typeオプションの値が1〜5じゃなかったらエラー
die "invalid value error" unless $config{type} > 0 and $config{type} < 6;

# -----
# 以下で様々な処理を行う

値のチェックが多くて、あまり本質的じゃない部分に時間が掛かってしまいます。。。

一つのスクリプトならまだ我慢できますが、このようなスクリプトをたくさん作る場合はどうでしょうか?値のチェックの部分を再利用しようにもあるスクリプトでは start と end で日付を指定したり、あるスクリプトでは type の取る値として 0 と 1 のみが許可されていたり・・・。何とかやれないことはないですが、かなりカオスです。

そこでこのように Moose を使ってみるのはどうでしょうか。

package MySample;

use Moose;
use MyTypes ':all';
with 'MooseX::Getopt';
 
has 'from' => ( is => 'rw', isa => 'ValidDate', required => 1 );
has 'to'   => ( is => 'rw', isa => 'ValidDate', required => 1 );
has 'type' => ( is => 'rw', isa => 'OnetoFive', default => 1 );
 
__PACKAGE__->meta->make_immutable; # おまじない
 
no Moose;
 
1;

これはコマンドライン引数について定義したクラスです。Moose を使う場合、has というメソッドを使って、is で ro(read only), rw(read and write) を指定したり、isa で Str(文字列), Int(整数) など、型を指定することが出来ます。例えば型として Int を指定した場合、「整数かどうか?」のチェックが行われ、文字列や小数を指定したときにはエラーが出力されます。

これだけでも便利ですが、isa には組み込みの型に加えて、自分で作ったものを利用することも出来ます。実は ValidDate や OnetoFive というのは自作の型だったりします。自作の型の定義は再利用できるようにするため、別クラスに切り分けました。

また、MySample では with というものを宣言しています。これは Moose の Role という便利な機能です。Role について今回は詳しくは説明しませんが、こちらに詳しく説明されているので良ければご覧ください。

package MyTypes;

use DateTime;
use MooseX::Types -declare => [ qw/ValidDate OnetoFive/ ];
use Moose::Util::TypeConstraints;

# YYYY-MM-DD形式で、存在する日付じゃなければエラー
subtype 'ValidDate'
    => as 'Str'
    => where {
        my @date = split /-/, $_;
        DateTime->new(
            year  => $date[0],
            month => $date[1],
            day   => $date[2],
        );
    };

# 整数で、1〜5じゃなければエラー
subtype 'OnetoFive'
    => as 'Int'
    => where {
        $_ > 0 and $_ < 6;
    };

no Moose::Util::TypeConstraints;

1;

これは自作の型を定義したクラスです。例えば、ValidDate という型はまず as で指定した型である Str(文字列) を継承します(文字列かどうかのチェックを行います)。その後、where で指定したチェックを行います。これらのチェックで問題があればエラーメッセージが表示されるわけです。

#!/usr/bin/perl
use MySample;
my $config = MySample->new_with_options();
 
# -----
# 以下で様々な処理を行う

これがメインのスクリプトです。とてもすっきりしました!! MySample を use して、new_with_opitonsメソッドを呼ぶだけです。必要な値のチェックは MySample クラスで定義済みなので自動でやってくれます。オプションの名前や条件が違う場合は、別クラスを作って、その中で任意の引数名や isa を指定してあげて、それを use すればOKです。

ちなみに 以下のように MySample を少し変更することでyamlファイルの設定を同時に読み込んだりも可能です。yamlファイルとコマンドライン引数で同じオプションを指定した場合、コマンドライン引数が優先されます。デフォルトの設定をyamlファイルに書いておくと便利そうですね!

package MySample;

use Config::Any; # 追加
use Moose;
use MyTypes ':all';
with 'MooseX::Getopt';
with 'MooseX::ConfigFromFile'; # 追加

has 'from' => ( is => 'rw', isa => 'ValidDate', required => 1 );
has 'to'   => ( is => 'rw', isa => 'ValidDate', required => 1 );
has 'type' => ( is => 'rw', isa => 'OnetoFive', default => 1 );
has '+configfile' => ( default => '/etc/foo.yaml' ); # 追加

__PACKAGE__->meta->make_immutable; # おまじない

no Moose;

# 追加
sub get_config_from_file {
    my ($self, $file) = @_;
    my $config = Config::Any->load_files({
        files   => [ $file ],
        use_ext => 1,
    });
    return $config->[0]->{$file};
};

1;

Moose はとっつきにくいかもしれませんが、慣れればとても便利ですので、是非使ってみてください。でわでわ〜ヾ(o゚ω゚o)ノ゙