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もお楽しみに!