Perlコンテキストとは何か
Perlを学ぶ上で避けて通れない、しかし理解すると世界が広がる概念が「コンテキスト(context)」です。
コンテキストとは、式や変数がどのような状況で評価されるかを示すものです。同じ式でも、評価されるコンテキストによって返される値が変わります。これはPerlの「TMTOWTDI(There’s More Than One Way To Do It)」の精神を支える重要な仕組みです。
Perlには主に3つのコンテキストがあります:
- スカラーコンテキスト(Scalar Context): 単一の値を期待する状況
- リストコンテキスト(List Context): 複数の値を期待する状況
- voidコンテキスト(Void Context): 戻り値を使わない状況
この記事では、これらのコンテキストを完全に理解し、実践的に活用する方法を学んでいきます。
スカラーコンテキスト
スカラーコンテキストは、単一の値(スカラー値)を期待する状況です。
スカラーコンテキストが発生する場面
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 代入の右辺でスカラー変数に代入
my $count = @array; # 配列の要素数が入る
# if文の条件式
if (@array) { # 要素数が真偽値として評価される
print "配列に要素があります\n";
}
# 文字列連結
my $str = "要素数は " . @array; # 配列が要素数に変換される
# 数値演算
my $result = @array + 10; # 配列の要素数 + 10
|
配列のスカラーコンテキスト
配列をスカラーコンテキストで評価すると、要素数を返します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
my @fruits = ('apple', 'banana', 'cherry');
my $count = @fruits; # $count = 3
print "果物の種類: $count\n"; # 果物の種類: 3
# 真偽値としての評価(要素数が0なら偽、それ以外は真)
if (@fruits) {
print "果物があります\n";
}
my @empty = ();
if (!@empty) {
print "空の配列です\n";
}
|
これは非常に便利で、配列が空かどうかをチェックする時によく使われます:
1
2
3
4
5
6
7
8
9
|
# 良い書き方
if (@array) {
# 配列に要素がある
}
# 冗長な書き方(避けるべき)
if (scalar(@array) > 0) {
# 配列に要素がある
}
|
ハッシュのスカラーコンテキスト
ハッシュをスカラーコンテキストで評価すると、少し特殊な値を返します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
my %hash = (a => 1, b => 2, c => 3);
my $scalar = %hash;
print $scalar, "\n"; # 例: "3/8" (使用バケット数/総バケット数)
# 真偽値としての評価(要素があれば真、空なら偽)
if (%hash) {
print "ハッシュに要素があります\n";
}
my %empty = ();
if (!%empty) {
print "空のハッシュです\n";
}
|
ハッシュのスカラー評価は、主に空かどうかの判定に使われます。バケット情報は通常は気にしません。
正規表現のスカラーコンテキスト
正規表現マッチングのスカラーコンテキストは、**マッチしたかどうか(真偽値)**を返します:
1
2
3
4
5
6
7
8
9
10
11
12
|
my $text = "Hello, World!";
# スカラーコンテキスト: マッチの成否
if ($text =~ /World/) {
print "マッチしました\n";
}
# マッチした文字列を取得したい場合
if ($text =~ /(W\w+)/) {
my $matched = $1; # "World"
print "マッチした単語: $matched\n";
}
|
リストコンテキスト
リストコンテキストは、複数の値を期待する状況です。これがPerlの強力さの源泉の一つです。
リストコンテキストが発生する場面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 配列への代入
my @array = function(); # function()はリストコンテキストで評価される
# リストへの代入
my ($x, $y, $z) = function(); # 複数の値を受け取る
# foreach ループ
foreach my $item (function()) { # リストを反復処理
print "$item\n";
}
# 関数の引数(括弧内)
print function(); # function()の結果をすべて表示
# 配列の初期化
my @result = (1, 2, function(), 3); # function()の結果が展開される
|
配列のリストコンテキスト
配列をリストコンテキストで評価すると、全要素のリストを返します:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
my @fruits = ('apple', 'banana', 'cherry');
# 別の配列にコピー
my @copy = @fruits; # ('apple', 'banana', 'cherry')
# 配列の要素を展開
my @combined = ('orange', @fruits, 'grape');
# ('orange', 'apple', 'banana', 'cherry', 'grape')
# foreach で反復処理
foreach my $fruit (@fruits) {
print "$fruit\n";
}
|
ハッシュのリストコンテキスト
ハッシュをリストコンテキストで評価すると、キーと値のペアのリストを返します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
my %person = (
name => 'Alice',
age => 30,
city => 'Tokyo',
);
# リストとして展開
my @list = %person;
# ('name', 'Alice', 'age', 30, 'city', 'Tokyo')
# 順序は保証されない!
# 別のハッシュにコピー
my %copy = %person;
# ハッシュのマージ
my %merged = (%person, job => 'Engineer');
|
重要: ハッシュをリストに展開した時の順序は保証されません。内部のハッシュ関数の実装に依存します。
正規表現のリストコンテキスト
正規表現マッチングのリストコンテキストは、キャプチャされた値のリストを返します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
my $text = "2025-12-08";
# リストコンテキスト: キャプチャされた値のリスト
my ($year, $month, $day) = $text =~ /(\d{4})-(\d{2})-(\d{2})/;
print "年: $year, 月: $month, 日: $day\n";
# 年: 2025, 月: 12, 日: 08
# グローバルマッチ
my @words = "apple banana cherry" =~ /(\w+)/g;
# @words = ('apple', 'banana', 'cherry')
# マッチしなかった場合は空リスト
my @no_match = "abc" =~ /(\d+)/;
# @no_match = ()
|
これは非常に強力で、テキスト処理でよく使われるイディオムです:
1
2
3
4
5
6
7
8
9
10
|
# CSVのような形式をパース
my $line = "Alice,30,Tokyo";
my ($name, $age, $city) = split /,/, $line;
# URLからパラメータを抽出
my $url = "http://example.com/user/123";
if ($url =~ m{/user/(\d+)}) {
my $user_id = $1;
print "User ID: $user_id\n";
}
|
voidコンテキスト
voidコンテキストは、式の戻り値が使われない状況です。
1
2
3
4
5
6
7
8
|
# 関数呼び出しの結果を使わない
function(); # voidコンテキスト
# 代入文全体(代入の結果は使われない)
$x = 10; # voidコンテキスト
# 式の結果を捨てる
@array; # voidコンテキスト(意味はない)
|
voidコンテキストでは、一部の演算子は警告を出すことがあります:
1
2
3
4
5
6
7
8
|
use warnings;
# 警告: Useless use of hash in void context
%hash;
# 警告が出ない(副作用がある)
print "Hello\n"; # 出力という副作用がある
push @array, 1; # 配列を変更する副作用がある
|
コンテキストを明示的に指定する
Perlには、コンテキストを明示的に指定する関数があります。
scalar() 関数
scalar() は、引数を強制的にスカラーコンテキストで評価します:
1
2
3
4
5
6
7
8
9
10
11
|
my @array = (1, 2, 3, 4, 5);
# リストコンテキストで評価(配列の要素をコピー)
my @copy = @array; # (1, 2, 3, 4, 5)
# スカラーコンテキストで評価(要素数を取得)
my $count = scalar @array; # 5
# 例: ハッシュのキーの数を取得
my %hash = (a => 1, b => 2, c => 3);
my $key_count = scalar keys %hash; # 3
|
リストコンテキストの強制
リストコンテキストを強制する組み込み関数はありませんが、配列に代入することで実現できます:
1
2
3
4
5
6
7
8
|
# スカラー変数に配列を代入(スカラーコンテキスト)
my $x = @array; # 要素数
# 配列変数に配列を代入(リストコンテキスト)
my @y = @array; # 全要素のコピー
# リストを括弧で囲んでもリストコンテキストになる
my ($z) = @array; # 最初の要素だけを取得
|
最後の例は重要です:
1
2
3
4
5
6
7
|
my @values = (10, 20, 30);
# スカラーコンテキスト
my $first = @values; # 3(要素数)
# リストコンテキスト(最初の要素だけを取得)
my ($first) = @values; # 10
|
wantarray() による柔軟な関数実装
wantarray() は、関数が呼び出されたコンテキストを判定する特殊な関数です。
wantarray() の戻り値
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
sub context_aware {
if (wantarray) {
# リストコンテキストで呼ばれた
return ('list', 'context');
} elsif (defined wantarray) {
# スカラーコンテキストで呼ばれた
return 'scalar context';
} else {
# voidコンテキストで呼ばれた
print "void context\n";
return;
}
}
# テスト
my @list = context_aware(); # @list = ('list', 'context')
my $scalar = context_aware(); # $scalar = 'scalar context'
context_aware(); # 出力: void context
|
実用的な例1: ファイル読み込み
組み込みの <> 演算子も、コンテキストに応じて動作が変わります:
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
|
# スカラーコンテキスト: 1行だけ読む
my $line = <STDIN>;
# リストコンテキスト: 全行を読む
my @lines = <STDIN>;
# これを自分で実装すると:
sub read_file {
my ($filename) = @_;
open my $fh, '<', $filename or die "Cannot open $filename: $!";
if (wantarray) {
# リストコンテキスト: 全行を返す
my @lines = <$fh>;
close $fh;
return @lines;
} else {
# スカラーコンテキスト: 最初の行だけ返す
my $line = <$fh>;
close $fh;
return $line;
}
}
# 使用例
my $first_line = read_file('data.txt'); # 最初の行だけ
my @all_lines = read_file('data.txt'); # 全行
|
実用的な例2: データベースクエリ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
sub fetch_users {
my ($dbh, $condition) = @_;
my $sth = $dbh->prepare("SELECT * FROM users WHERE $condition");
$sth->execute();
if (wantarray) {
# リストコンテキスト: 全レコードを返す
my @users;
while (my $row = $sth->fetchrow_hashref) {
push @users, $row;
}
return @users;
} else {
# スカラーコンテキスト: 最初の1件だけ返す
return $sth->fetchrow_hashref;
}
}
# 使用例
my $user = fetch_users($dbh, "id = 1"); # 1件だけ取得
my @users = fetch_users($dbh, "age > 20"); # 全件取得
|
実用的な例3: 検索関数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
sub find_items {
my ($pattern, @items) = @_;
my @matches = grep { /$pattern/ } @items;
if (wantarray) {
# リストコンテキスト: マッチした全要素を返す
return @matches;
} elsif (defined wantarray) {
# スカラーコンテキスト: マッチした個数を返す
return scalar @matches;
} else {
# voidコンテキスト: 何もしない(警告を出すこともできる)
warn "find_items called in void context";
return;
}
}
# 使用例
my @found = find_items('perl', @files); # マッチしたファイル名のリスト
my $count = find_items('perl', @files); # マッチした個数
find_items('perl', @files); # 警告が出る
|
実用的な例4: 統計関数
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
|
sub analyze_numbers {
my (@numbers) = @_;
return unless @numbers;
my $sum = 0;
$sum += $_ for @numbers;
my $avg = $sum / @numbers;
my @sorted = sort { $a <=> $b } @numbers;
my $min = $sorted[0];
my $max = $sorted[-1];
my $median = @sorted % 2
? $sorted[@sorted/2]
: ($sorted[@sorted/2-1] + $sorted[@sorted/2]) / 2;
if (wantarray) {
# リストコンテキスト: 詳細な統計情報を返す
return (
sum => $sum,
avg => $avg,
min => $min,
max => $max,
median => $median,
count => scalar @numbers,
);
} else {
# スカラーコンテキスト: 平均値のみを返す
return $avg;
}
}
# 使用例
my @data = (10, 20, 30, 40, 50);
my $average = analyze_numbers(@data); # 30
my %stats = analyze_numbers(@data); # 詳細な統計情報
print "平均: $stats{avg}, 中央値: $stats{median}\n";
|
コンテキストと演算子
多くのPerlの演算子は、コンテキストによって動作が変わります。
代入演算子
1
2
3
4
5
6
7
8
9
10
|
my @array = (1, 2, 3);
# スカラーコンテキスト
my $x = @array; # $x = 3(要素数)
# リストコンテキスト
my @y = @array; # @y = (1, 2, 3)
# リストの最初の要素だけを取得
my ($first) = @array; # $first = 1
|
range演算子 (..)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# リストコンテキスト: 範囲のリストを生成
my @numbers = (1..10); # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
# スカラーコンテキスト: フリップフロップ演算子として動作
# (行範囲演算子として使われることが多い)
while (<DATA>) {
print if /START/ .. /END/; # STARTからENDまでの行を出力
}
__DATA__
header
START
line1
line2
END
footer
|
reverse関数
1
2
3
4
5
6
7
8
|
my @array = (1, 2, 3, 4, 5);
# リストコンテキスト: 配列の順序を逆にする
my @reversed = reverse @array; # (5, 4, 3, 2, 1)
# スカラーコンテキスト: 文字列を逆にする
my $str = "Hello";
my $reversed_str = reverse $str; # "olleH"
|
これは注意が必要です:
1
2
3
4
5
6
7
|
my @array = (1, 2, 3);
# これは期待通りに動作しない!
my $result = reverse @array; # $result = "321"(文字列として連結される)
# 配列を逆順にしたい場合は、明示的にリストコンテキストで
my @reversed = reverse @array; # (3, 2, 1)
|
sort関数
1
2
3
4
5
6
7
8
9
10
|
my @numbers = (5, 2, 8, 1, 9);
# リストコンテキスト: ソートされた配列を返す
my @sorted = sort { $a <=> $b } @numbers; # (1, 2, 5, 8, 9)
# スカラーコンテキスト: 最初の要素を返す
my $first = sort { $a <=> $b } @numbers; # 1
# ただし、スカラーコンテキストでsortを使うのは非効率
# なぜなら、内部では全要素をソートしてから最初の要素だけを返すため
|
map と grep
1
2
3
4
5
6
7
8
9
10
11
12
|
my @numbers = (1, 2, 3, 4, 5);
# リストコンテキスト: 変換/フィルタされた配列を返す
my @doubled = map { $_ * 2 } @numbers; # (2, 4, 6, 8, 10)
my @evens = grep { $_ % 2 == 0 } @numbers; # (2, 4)
# スカラーコンテキスト: 要素数を返す
my $count = grep { $_ % 2 == 0 } @numbers; # 2(偶数の個数)
# mapのスカラーコンテキストは注意が必要
my $last = map { $_ * 2 } @numbers; # 10(最後の要素)
# これは分かりにくいので避けるべき
|
よくある誤解と落とし穴
誤解1: 配列代入は常にコピーする
1
2
3
4
5
6
7
8
9
|
my @original = (1, 2, 3);
# これはコピー
my @copy = @original;
$copy[0] = 999;
print $original[0]; # 1(変更されない)
# これも実はコピー(リファレンスではない)
my $ref = \@original; # これがリファレンス
|
誤解2: スカラー変数に配列を代入すると最初の要素が入る
1
2
3
4
5
6
7
8
9
|
my @array = (1, 2, 3);
# 誤解: 最初の要素が入ると思う
my $x = @array; # $x = 3(要素数)
# 最初の要素を取りたい場合
my ($first) = @array; # $first = 1
# または
my $first = $array[0]; # $first = 1
|
誤解3: ハッシュのスカラー評価で要素数が取れる
1
2
3
4
5
6
7
|
my %hash = (a => 1, b => 2, c => 3);
# ハッシュのスカラー評価は要素数ではない!
my $x = %hash; # "3/8" のようなバケット情報
# 要素数が欲しい場合はkeys()を使う
my $count = scalar keys %hash; # 3
|
落とし穴1: リストのフラット化
Perlのリストは常にフラット化されます:
1
2
3
4
5
6
7
8
9
|
my @a = (1, 2);
my @b = (3, 4);
my @c = (@a, @b); # (1, 2, 3, 4) - フラット化される
# 多次元配列を作りたい場合はリファレンスを使う
my @matrix = (
[1, 2],
[3, 4],
);
|
落とし穴2: 関数の引数は常にフラット化される
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
sub print_arrays {
my (@arr1, @arr2) = @_; # これは期待通りに動作しない!
# @arr1 がすべての引数を受け取り、@arr2 は空になる
}
my @a = (1, 2);
my @b = (3, 4);
print_arrays(@a, @b); # @arr1 = (1, 2, 3, 4), @arr2 = ()
# 複数の配列を渡したい場合はリファレンスを使う
sub print_arrays_correct {
my ($arr1_ref, $arr2_ref) = @_;
my @arr1 = @$arr1_ref;
my @arr2 = @$arr2_ref;
# ...
}
print_arrays_correct(\@a, \@b);
|
落とし穴3: 正規表現のグローバルマッチ
1
2
3
4
5
6
7
8
9
10
11
12
13
|
my $text = "apple banana cherry";
# スカラーコンテキストでのグローバルマッチは反復的に動作する
while ($text =~ /(\w+)/g) {
print "Found: $1\n";
}
# Found: apple
# Found: banana
# Found: cherry
# リストコンテキストでは一度に全部マッチする
my @words = $text =~ /(\w+)/g;
# @words = ('apple', 'banana', 'cherry')
|
落とし穴4: voidコンテキストでの警告
1
2
3
4
5
6
7
8
9
10
11
12
13
|
use warnings;
my @array = (1, 2, 3);
# 警告: Useless use of array in void context
@array;
# これも同様
my %hash = (a => 1);
%hash; # 警告
# 副作用がある式なら警告は出ない
print @array; # OK(出力という副作用がある)
|
実践的なコンテキスト活用パターン
パターン1: 柔軟なゲッター
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
|
package User;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
return bless \%args, $class;
}
sub roles {
my $self = shift;
if (wantarray) {
# リストコンテキスト: ロールのリストを返す
return @{$self->{roles} || []};
} else {
# スカラーコンテキスト: ロールの数を返す
return scalar @{$self->{roles} || []};
}
}
# 使用例
my $user = User->new(roles => ['admin', 'editor', 'user']);
my @roles = $user->roles; # ('admin', 'editor', 'user')
my $count = $user->roles; # 3
if ($user->roles > 1) {
print "複数のロールを持っています\n";
}
|
パターン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
|
sub search {
my ($pattern, $data_ref) = @_;
my @results;
for my $item (@$data_ref) {
if ($item =~ /$pattern/) {
if (wantarray) {
# リストコンテキスト: マッチした項目を収集
push @results, $item;
} else {
# スカラーコンテキスト: 最初のマッチを即座に返す
return $item;
}
}
}
return wantarray ? @results : undef;
}
# 大きなデータセット
my @data = (1..1_000_000);
# 最初の1件だけ欲しい(効率的)
my $first = search(qr/^999/, \@data);
# 全件欲しい
my @all = search(qr/^999/, \@data);
|
パターン3: 設定の取得
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
|
package Config;
use strict;
use warnings;
my %config = (
database => {
host => 'localhost',
port => 5432,
name => 'mydb',
},
cache => {
enabled => 1,
ttl => 3600,
},
);
sub get {
my ($class, $key) = @_;
my $value = $config{$key};
if (ref $value eq 'HASH') {
if (wantarray) {
# リストコンテキスト: ハッシュをキー・値のリストとして返す
return %$value;
} else {
# スカラーコンテキスト: ハッシュリファレンスを返す
return $value;
}
}
return $value;
}
# 使用例
my %db_config = Config->get('database'); # ハッシュとしてコピー
my $db_ref = Config->get('database'); # リファレンスとして取得
my $host = $db_ref->{host};
|
パターン4: 日時の解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
use Time::Piece;
sub parse_datetime {
my ($str) = @_;
my $t = Time::Piece->strptime($str, '%Y-%m-%d %H:%M:%S');
if (wantarray) {
# リストコンテキスト: 年月日時分秒を個別に返す
return ($t->year, $t->mon, $t->mday,
$t->hour, $t->min, $t->sec);
} else {
# スカラーコンテキスト: Time::Pieceオブジェクトを返す
return $t;
}
}
# 使用例
my $dt = parse_datetime('2025-12-08 15:30:45');
print $dt->strftime('%Y年%m月%d日');
my ($year, $month, $day, $hour, $min, $sec)
= parse_datetime('2025-12-08 15:30:45');
print "年: $year, 月: $month, 日: $day\n";
|
まとめ
Perlのコンテキスト概念は、最初は戸惑うかもしれませんが、理解すると非常に強力なツールになります。
重要なポイント
-
3つのコンテキスト
- スカラーコンテキスト: 単一の値を期待
- リストコンテキスト: 複数の値を期待
- voidコンテキスト: 戻り値を使わない
-
配列とハッシュ
- 配列のスカラー評価 = 要素数
- ハッシュのスカラー評価 = バケット情報(通常は気にしない)
- 空チェックには
if (@array) や if (%hash) が便利
-
正規表現
- スカラーコンテキスト = マッチの成否(真偽値)
- リストコンテキスト = キャプチャされた値のリスト
-
wantarray()
- リストコンテキスト: 真を返す
- スカラーコンテキスト: 定義された偽を返す
- voidコンテキスト: 未定義値を返す
-
実用的なパターン
wantarray() を使って柔軟な関数を実装
- スカラーコンテキストでは簡潔な値を返す
- リストコンテキストでは詳細な情報を返す
コンテキストを意識したコーディング
Perlのコンテキストを理解することで、より表現力豊かなコードが書けるようになります:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# コンテキストを活かした美しいコード
my @sorted = sort { $a <=> $b } @numbers; # リストコンテキスト
my $count = grep { $_ > 10 } @numbers; # スカラーコンテキスト
# 配列の空チェック
if (@array) { ... }
# 最初の要素を取得
my ($first) = @array;
# wantarray()で柔軟な関数
sub flexible_function {
return wantarray ? (1, 2, 3) : 3;
}
|
コンテキストはPerlの魔法の一つです。この概念を使いこなせば、あなたのPerlコードはより洗練され、表現力豊かなものになるでしょう!
Happy Coding! 🎄