Featured image of post CPANとは — 実務で役立つCPANモジュール20選

CPANとは — 実務で役立つCPANモジュール20選

CPANの概要と、実務で本当に役立つCPANモジュール20選をカテゴリ別に紹介します。

CPANとは

CPAN(Comprehensive Perl Archive Network)は、Perlの最大の強みの一つです。世界中の開発者が作成した18万以上のモジュールが公開されており、ほとんどの問題に対して「すでに誰かが解決している」状態です。Perlの格言「車輪の再発明をするな」を体現するエコシステムと言えるでしょう。

本記事では、実務で本当に役立つCPANモジュール20選を、カテゴリ別に紹介します。すべて現場で使われている実績のあるモジュールばかりです。

モジュールのインストール方法

CPANモジュールはcpanm(App::cpanminus)を使ってインストールするのが最も簡単です:

1
2
3
4
5
6
# cpanm自体のインストール
curl -L https://cpanmin.us | perl - App::cpanminus

# モジュールのインストール
cpanm Path::Tiny
cpanm Mojolicious

実際のプロジェクトでは、依存関係をcpanfileで管理するのがベストプラクティスです:

1
2
3
4
5
6
7
# cpanfile
requires 'Path::Tiny', '>= 0.144';
requires 'Mojolicious', '>= 9.0';
requires 'Time::Moment';

# インストール
cpanm --installdeps .

ファイル・パス操作

1. Path::Tiny

選定理由: ファイル操作の定番。File::SpecやFile::Slurpの複雑さを解消し、直感的なAPIを提供します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use Path::Tiny;

# ファイルの読み込み
my $content = path('config.txt')->slurp_utf8;

# ファイルへの書き込み
path('output.txt')->spew_utf8("Hello, World!\n");

# ディレクトリの再帰的な走査
my $iter = path('lib')->iterator({ recurse => 1 });
while (my $path = $iter->()) {
    next unless $path =~ /\.pm$/;
    say $path;
}

# ファイルの存在確認と作成
my $file = path('data/cache.json');
$file->parent->mkpath;  # 親ディレクトリを作成
$file->touch unless $file->exists;

特徴:

  • メソッドチェーンで直感的
  • UTF-8対応が簡単
  • Windows/Unixのパス差異を自動処理

2. File::Find::Rule

選定理由: ファイル検索を宣言的に書ける。複雑な条件でのファイル検索が劇的に簡単になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use File::Find::Rule;

# 特定の拡張子のファイルを検索
my @files = File::Find::Rule->file()
                            ->name('*.pl', '*.pm')
                            ->in('lib', 't');

# サイズと更新日時での検索
my @large_files = File::Find::Rule->file()
                                  ->size('>1M')
                                  ->mtime('>7')  # 7日以内に更新
                                  ->in('.');

Web関連

3. HTTP::Tiny

選定理由: コアモジュール(Perl 5.14+)でありながら実用的。外部依存なしでHTTPリクエストが可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use HTTP::Tiny;

my $http = HTTP::Tiny->new(timeout => 10);

# GETリクエスト
my $response = $http->get('https://api.example.com/users');
if ($response->{success}) {
    say $response->{content};
}

# POSTリクエスト(JSON)
my $res = $http->post(
    'https://api.example.com/posts',
    {
        headers => { 'Content-Type' => 'application/json' },
        content => '{"title":"Hello","body":"World"}',
    }
);

4. Mojo::UserAgent

選定理由: 強力な非同期対応とDOMパーサー内蔵。スクレイピングやAPI連携に最適です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

# シンプルなGET
my $res = $ua->get('https://example.com')->result;
say $res->body;

# DOMセレクタでスクレイピング
$ua->get('https://news.ycombinator.com')->result
   ->dom->find('.titleline > a')->each(sub {
       say $_->text, ' => ', $_->attr('href');
   });

# JSON API
my $json = $ua->get('https://api.github.com/users/perl')
              ->result->json;
say $json->{name};

5. Plack

選定理由: WebアプリケーションのPSGI標準実装。フレームワークに依存しないWebアプリ開発の基盤です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# app.psgi
use Plack::Request;

my $app = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    
    return [
        200,
        ['Content-Type' => 'text/plain'],
        ["Hello, " . ($req->param('name') || 'World')]
    ];
};

# 起動: plackup app.psgi

データ処理

6. JSON::MaybeXS

選定理由: 最速のJSONモジュールを自動選択。JSON::XS(C実装)があれば使い、なければPure Perlにフォールバックします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use JSON::MaybeXS;

my $json = JSON::MaybeXS->new(utf8 => 1, pretty => 1);

# エンコード
my $data = { name => '太郎', age => 30, hobbies => ['Perl', 'CPAN'] };
my $json_text = $json->encode($data);

# デコード
my $decoded = $json->decode($json_text);
say $decoded->{name};  # 太郎

7. YAML::XS

選定理由: YAML 1.1対応の高速パーサー。設定ファイルの読み書きに最適です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use YAML::XS qw(LoadFile DumpFile);

# YAMLファイルの読み込み
my $config = LoadFile('config.yml');

# YAMLファイルへの書き込み
DumpFile('output.yml', {
    database => {
        host => 'localhost',
        port => 5432,
    },
    features => ['cache', 'logging'],
});

8. XML::LibXML

選定理由: libxml2ベースの高速XMLパーサー。XPathとXSLT対応で複雑なXML処理も可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use XML::LibXML;

my $parser = XML::LibXML->new;
my $doc = $parser->parse_file('data.xml');

# XPathで要素を検索
for my $node ($doc->findnodes('//book[@category="programming"]')) {
    my $title = $node->findvalue('./title');
    my $author = $node->findvalue('./author');
    say "$title by $author";
}

# 新しい要素を追加
my $new_book = $doc->createElement('book');
$new_book->setAttribute('category', 'perl');
my $title = $doc->createElement('title');
$title->appendText('Programming Perl');
$new_book->appendChild($title);

9. Text::CSV_XS

選定理由: CSVパーサーの決定版。RFC 4180準拠で、複雑なCSVも正確に処理します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use Text::CSV_XS;

my $csv = Text::CSV_XS->new({ binary => 1, auto_diag => 1 });

# CSV読み込み
open my $fh, '<:encoding(utf8)', 'data.csv' or die $!;
while (my $row = $csv->getline($fh)) {
    say join ', ', @$row;
}

# CSV書き込み
open my $out, '>:encoding(utf8)', 'output.csv' or die $!;
$csv->say($out, ['Name', 'Age', 'Email']);
$csv->say($out, ['太郎', 30, 'taro@example.com']);
close $out;

日時処理

10. Time::Moment

選定理由: 最速の日時処理ライブラリ。不変オブジェクトで安全、かつ高性能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Time::Moment;

# 現在時刻
my $now = Time::Moment->now_utc;
say $now->to_string;  # 2025-12-03T12:34:56Z

# 特定の日時を作成
my $tm = Time::Moment->new(
    year   => 2025,
    month  => 12,
    day    => 3,
    hour   => 15,
    minute => 30,
);

# 日時演算
my $tomorrow = $tm->plus_days(1);
my $next_week = $tm->plus_weeks(1);
my $diff_seconds = $tomorrow->delta_seconds($now);

# フォーマット
say $tm->strftime('%Y年%m月%d日 %H:%M');

11. DateTime

選定理由: タイムゾーン対応が必要な場合の定番。豊富な機能と拡張モジュール群があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use DateTime;

my $dt = DateTime->new(
    year      => 2025,
    month     => 12,
    day       => 3,
    hour      => 15,
    time_zone => 'Asia/Tokyo',
);

# タイムゾーン変換
my $utc = $dt->clone->set_time_zone('UTC');
say $utc->ymd . ' ' . $utc->hms;

# 相対的な日付
my $last_month = $dt->clone->subtract(months => 1);

データベース

12. DBI

選定理由: Perlのデータベース接続の標準。あらゆるデータベースに統一的なインターフェースでアクセスできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use DBI;

my $dbh = DBI->connect(
    'dbi:SQLite:dbname=test.db',
    '', '',
    { RaiseError => 1, AutoCommit => 1 }
);

# プレースホルダを使った安全なクエリ
my $sth = $dbh->prepare('SELECT * FROM users WHERE age > ?');
$sth->execute(20);

while (my $row = $sth->fetchrow_hashref) {
    say "$row->{name} ($row->{age})";
}

# 挿入
$dbh->do('INSERT INTO users (name, age) VALUES (?, ?)', undef, '太郎', 30);

13. DBIx::Class

選定理由: PerlのO/Rマッパーの最高峰。複雑なクエリをオブジェクト指向で記述できます。

 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
# スキーマ定義(簡略版)
package MyApp::Schema::Result::User;
use base 'DBIx::Class::Core';
__PACKAGE__->table('users');
__PACKAGE__->add_columns(qw/id name email created_at/);
__PACKAGE__->set_primary_key('id');

# 使用例
my $schema = MyApp::Schema->connect('dbi:SQLite:test.db');

# 検索
my $users = $schema->resultset('User')->search(
    { created_at => { '>' => '2025-01-01' } },
    { order_by => { -desc => 'created_at' } }
);

while (my $user = $users->next) {
    say $user->name;
}

# 作成
my $new_user = $schema->resultset('User')->create({
    name  => '花子',
    email => 'hanako@example.com',
});

オブジェクト指向

14. Moo

選定理由: 軽量で高速なモダンなオブジェクトシステム。ほとんどのケースでこれで十分です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Person;
use Moo;

has name => (is => 'ro', required => 1);
has age  => (is => 'rw', default => 0);
has email => (
    is  => 'ro',
    isa => sub { die "Invalid email" unless $_[0] =~ /@/ },
);

sub greet {
    my $self = shift;
    return "Hello, I'm " . $self->name;
}

# 使用例
my $person = Person->new(
    name  => '太郎',
    age   => 30,
    email => 'taro@example.com',
);
say $person->greet;
$person->age(31);

15. Moose

選定理由: フル機能のオブジェクトシステム。型制約、ロール、メタプログラミングなど高度な機能が必要な場合に。

 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
29
30
31
32
33
package BankAccount;
use Moose;
use Moose::Util::TypeConstraints;

subtype 'PositiveNum',
    as 'Num',
    where { $_ >= 0 },
    message { "Balance must be positive" };

has 'balance' => (
    is      => 'rw',
    isa     => 'PositiveNum',
    default => 0,
);

has 'owner' => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

sub deposit {
    my ($self, $amount) = @_;
    $self->balance($self->balance + $amount);
}

sub withdraw {
    my ($self, $amount) = @_;
    die "Insufficient funds" if $self->balance < $amount;
    $self->balance($self->balance - $amount);
}

__PACKAGE__->meta->make_immutable;

テスト

16. Test2::Suite

選定理由: 次世代のテストフレームワーク。Test::Moreの後継で、より強力で拡張性があります。

 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
use Test2::V0;

# 基本的なテスト
ok(1, 'This passes');
is(1 + 1, 2, 'Math works');

# 構造体の比較
is(
    { name => 'Taro', age => 30 },
    { name => 'Taro', age => 30 },
    'Hash matches'
);

# 例外のテスト
like(
    dies { die "Error!" },
    qr/Error/,
    'Dies with correct message'
);

# サブテスト
subtest 'User tests' => sub {
    my $user = User->new(name => 'Test');
    ok($user, 'User created');
    is($user->name, 'Test', 'Name is correct');
};

done_testing;

17. Test::More

選定理由: コアモジュールのテストフレームワーク。シンプルで学習コストが低く、広く使われています。

1
2
3
4
5
6
7
8
use Test::More tests => 5;

ok(1, 'Always passes');
is('hello', 'hello', 'String equality');
isnt('foo', 'bar', 'Not equal');

like('abc123', qr/\d+/, 'Contains digits');
can_ok('MyClass', qw(new fetch update));

18. Devel::Cover

選定理由: コードカバレッジ測定の標準ツール。テストの網羅性を可視化できます。

1
2
3
4
5
# カバレッジ測定
cover -test

# HTMLレポート生成
cover -report html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# テストファイル内で特定の条件を網羅
use Test::More;

my @test_cases = (
    [1, 2, 3],
    [0, 0, 0],
    [-1, 5, 4],
);

for my $case (@test_cases) {
    my ($a, $b, $expected) = @$case;
    is(add($a, $b), $expected, "add($a, $b) = $expected");
}

done_testing;

ユーティリティ

19. List::Util / List::MoreUtils

選定理由: リスト操作の定番。List::Utilはコアモジュール、List::MoreUtilsはさらに便利な関数を提供します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use List::Util qw(first max min sum reduce any all);
use List::MoreUtils qw(uniq zip);

# List::Util
my @numbers = (1, 5, 3, 9, 2);
say max(@numbers);        # 9
say min(@numbers);        # 1
say sum(@numbers);        # 20

my $first_even = first { $_ % 2 == 0 } @numbers;  # 2

my $product = reduce { $a * $b } 1..5;  # 120

say "All positive" if all { $_ > 0 } @numbers;
say "Has even" if any { $_ % 2 == 0 } @numbers;

# List::MoreUtils
my @unique = uniq(1, 2, 2, 3, 3, 3);  # (1, 2, 3)

my @names = ('Alice', 'Bob', 'Carol');
my @ages = (30, 25, 35);
my @pairs = zip @names, @ages;  # ('Alice', 30, 'Bob', 25, 'Carol', 35)

20. Try::Tiny

選定理由: 安全な例外処理。evalブロックの罠を避け、読みやすいエラーハンドリングを実現します。

 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
use Try::Tiny;

try {
    my $result = risky_operation();
    process($result);
} catch {
    warn "Caught error: $_";
    # $_ には例外が入る
    
    if (/network/i) {
        say "Network error, retrying...";
        retry_with_backoff();
    } else {
        die $_;  # 再スロー
    }
} finally {
    cleanup_resources();
};

# データベース接続の例
try {
    my $dbh = DBI->connect($dsn, $user, $pass, { RaiseError => 1 });
    $dbh->do('UPDATE users SET active = 1 WHERE id = ?', undef, $user_id);
} catch {
    log_error("Database error: $_");
    send_alert_to_admin($_);
};

まとめ

今回紹介した20のモジュールは、Perl開発において実績のある「定番」です。これらを使いこなすことで、以下のような利点があります:

生産性の向上

  • 低レベルな処理を書く必要がなくなる
  • バグの少ない安定したコードが書ける
  • コミュニティで広く使われているため、情報が豊富

モダンなPerl開発

  • 古いPerl(Perl 4時代のイディオム)から脱却
  • オブジェクト指向やテスト駆動開発が容易に
  • メンテナンスしやすいコードベースを構築

ベストプラクティス

  • UTF-8対応が簡単に(Path::Tiny、Text::CSV_XS)
  • 例外処理が安全に(Try::Tiny)
  • 型安全性を確保(Moo/Mooseの型制約)

cpanfileでの管理例

最後に、これらのモジュールを使ったプロジェクトのcpanfile例を示します:

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
# cpanfile
requires 'perl', '5.024';  # Perl 5.24以上

# ファイル操作
requires 'Path::Tiny', '>= 0.144';
requires 'File::Find::Rule';

# Web
requires 'HTTP::Tiny';
requires 'Mojolicious', '>= 9.0';
requires 'Plack';

# データ処理
requires 'JSON::MaybeXS';
requires 'YAML::XS';
requires 'XML::LibXML';
requires 'Text::CSV_XS';

# 日時
requires 'Time::Moment';

# データベース
requires 'DBI';
requires 'DBD::SQLite';  # SQLite用ドライバ

# OO
requires 'Moo', '>= 2.0';

# ユーティリティ
requires 'List::MoreUtils';
requires 'Try::Tiny';

# テスト(開発時のみ)
on 'test' => sub {
    requires 'Test2::Suite';
    requires 'Test::More', '>= 1.302';
};

on 'develop' => sub {
    requires 'Devel::Cover';
};

さらに学ぶために

CPANの世界は広大です。今回紹介したモジュール以外にも、特定の用途に特化した優れたモジュールが無数に存在します:

  • Task::Kensho: モダンなPerl開発に推奨されるモジュール集
  • MetaCPAN: https://metacpan.org/ でモジュールを検索
  • CPAN Ratings: コミュニティの評価を参考に
  • YAPC: PerlのカンファレンスでCPAN作者から直接学ぶ

Perlの格言に「車輪の再発明をするな」という言葉があります。CPANを活用することで、本来解くべき問題に集中できるのです。

明日のPerl Advent Calendar Day 4もお楽しみに!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。