Featured image of post Perlのリファレンス — 基礎から実践まで

Perlのリファレンス — 基礎から実践まで

Perlのリファレンスの概念と使用法を、無名配列・無名ハッシュ・サブルーチンリファレンスなど実用例で丁寧に解説します。

はじめに - リファレンスはPerlの要

リファレンスは、Perlで複雑なデータ構造を扱うための最も重要な機能です。配列の配列、ハッシュのハッシュ、JSONのような階層的なデータ――これらはすべてリファレンスなしには実現できません。

Perl 5.0(1994年)でリファレンスが導入されたことで、Perlは本格的なオブジェクト指向プログラミングが可能になり、複雑なアプリケーション開発の基盤が整いました。リファレンスを理解することは、モダンなPerl開発の第一歩です。

この記事では、リファレンスの概念から始めて、実用的な使い方、デバッグ方法まで、リファレンスのすべてを解説します。

リファレンスとは何か

メモリとアドレスの概念

リファレンスを理解するには、まずコンピュータのメモリの仕組みを知る必要があります。

1
2
3
4
5
6
7
8
9
use v5.38;

# 通常の変数
my @array = (1, 2, 3);
my %hash = (name => 'Taro', age => 30);

# これらは実際のデータがメモリに格納されている
# @array → メモリ上のどこかに [1, 2, 3] が存在
# %hash  → メモリ上のどこかに {name => 'Taro', age => 30} が存在

リファレンスは、このメモリ上の「場所(アドレス)」を指し示すものです。C言語のポインタに似ていますが、Perlのリファレンスはより安全で使いやすく設計されています。

1
2
3
4
5
6
7
use v5.38;

my @array = (1, 2, 3);
my $ref = \@array;  # @array のアドレスを $ref に格納

say $ref;  # ARRAY(0x55a8b9c8d2e0) のような出力
           # 0x... の部分がメモリアドレス

なぜリファレンスが必要なのか

Perlでは、配列やハッシュの中に直接別の配列やハッシュを入れることはできません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use v5.38;

# これは期待通りに動かない!
my @matrix = (
    (1, 2, 3),    # カンマで区切られた要素として展開される
    (4, 5, 6),    # 結果: @matrix = (1, 2, 3, 4, 5, 6) になってしまう
    (7, 8, 9)
);

say scalar @matrix;  # 9(期待値は3ではない)

リファレンスを使えば、多次元データ構造を作れます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use v5.38;

# リファレンスを使った正しい方法
my @matrix = (
    [1, 2, 3],    # 配列リファレンス
    [4, 5, 6],    # 配列リファレンス
    [7, 8, 9]     # 配列リファレンス
);

say scalar @matrix;     # 3(正しい!)
say $matrix[0]->[1];    # 2(1行目2列目の要素)

リファレンスの作成方法

バックスラッシュ演算子(\)

既存の変数のリファレンスを作る最も基本的な方法です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use v5.38;

# スカラーリファレンス
my $value = 42;
my $scalar_ref = \$value;

# 配列リファレンス
my @array = (1, 2, 3);
my $array_ref = \@array;

# ハッシュリファレンス
my %hash = (key => 'value');
my $hash_ref = \%hash;

# サブルーチンリファレンス
sub greet { say "Hello!" }
my $sub_ref = \&greet;

無名配列([])

新しい配列を直接リファレンスとして作成します。

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

# 無名配列の作成
my $ref = [1, 2, 3, 4, 5];

# これは以下と同等
# my @temp = (1, 2, 3, 4, 5);
# my $ref = \@temp;
# ただし @temp は存在しない(無名)

# 多次元配列
my $matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

無名ハッシュ({})

新しいハッシュを直接リファレンスとして作成します。

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

# 無名ハッシュの作成
my $person = {
    name => 'Taro',
    age  => 30,
    city => 'Tokyo'
};

# 複雑な構造
my $company = {
    name      => 'Example Corp',
    employees => [
        { name => 'Alice', role => 'Engineer' },
        { name => 'Bob',   role => 'Designer' },
        { name => 'Carol', role => 'Manager' }
    ],
    founded   => 2020
};

無名サブルーチン(sub {})

サブルーチンを直接リファレンスとして作成します。

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

# 無名サブルーチンのリファレンス
my $greet = sub {
    my ($name) = @_;
    say "Hello, $name!";
};

$greet->('World');  # Hello, World!

# クロージャの例
sub make_counter {
    my $count = 0;
    return sub {
        return ++$count;
    };
}

my $counter = make_counter();
say $counter->();  # 1
say $counter->();  # 2
say $counter->();  # 3

デリファレンス - リファレンスから実体へ

リファレンスから元のデータにアクセスすることを「デリファレンス」と呼びます。

矢印演算子(->)- 最も一般的な方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use v5.38;

my $array_ref = [10, 20, 30];
my $hash_ref  = { x => 100, y => 200 };

# 配列要素へのアクセス
say $array_ref->[0];     # 10
say $array_ref->[1];     # 20

# ハッシュ要素へのアクセス
say $hash_ref->{x};      # 100
say $hash_ref->{y};      # 200

# 多次元の場合、矢印は省略可能
my $matrix = [[1, 2], [3, 4]];
say $matrix->[0]->[1];   # 2
say $matrix->[0][1];     # 2(同じ意味、矢印省略)

中括弧デリファレンス({})

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use v5.38;

my $array_ref = [1, 2, 3];
my $hash_ref  = { a => 1, b => 2 };

# 配列全体にアクセス
my @array = @{$array_ref};
say "@array";  # 1 2 3

# ハッシュ全体にアクセス
my %hash = %{$hash_ref};
say $hash{a};  # 1

# スカラーの場合
my $value = 42;
my $scalar_ref = \$value;
say ${$scalar_ref};  # 42

配列・ハッシュの操作

 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 v5.38;

my $array_ref = [1, 2, 3];

# 配列操作
push @{$array_ref}, 4;
say "@{$array_ref}";  # 1 2 3 4

my $popped = pop @{$array_ref};
say $popped;  # 4

# 配列の長さ
say scalar @{$array_ref};  # 3

# ハッシュ操作
my $hash_ref = { a => 1, b => 2 };
$hash_ref->{c} = 3;

# キーの一覧
my @keys = keys %{$hash_ref};
say "@keys";  # a b c(順序は不定)

# 値の一覧
my @values = values %{$hash_ref};

ポストフィックスデリファレンス(モダンPerl)

Perl 5.20以降で使える、読みやすい新しい記法です。

 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
use v5.20;
use feature 'postderef';
no warnings 'experimental::postderef';

# または use v5.24 以降なら warnings 不要

my $array_ref = [1, 2, 3];
my $hash_ref  = { a => 1, b => 2 };

# 配列全体
my @array = $array_ref->@*;
say "@array";  # 1 2 3

# ハッシュ全体
my %hash = $hash_ref->%*;
say $hash{a};  # 1

# 配列操作
push $array_ref->@*, 4;
my $last = pop $array_ref->@*;

# ハッシュのキーと値
my @keys   = $hash_ref->%*;  # これは間違い
my @keys   = keys $hash_ref->%*;
my @values = values $hash_ref->%*;

# スライス
my @slice = $array_ref->@[0, 2];  # (1, 3)
my %slice = $hash_ref->%{qw(a c)};

Perl 5.24以降では experimental 警告なしで使えます。

1
2
3
4
use v5.24;

my $data = [1, 2, 3];
say join ', ', $data->@*;  # 1, 2, 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
use v5.38;

my $array_ref = [1, 2, 3];
my $hash_ref  = { a => 1 };
my $code_ref  = sub { say "hi" };

# ref 関数
say ref $array_ref;  # ARRAY
say ref $hash_ref;   # HASH
say ref $code_ref;   # CODE

# 判定
if (ref $array_ref eq 'ARRAY') {
    say "This is an array reference";
}

# Scalar::Util::reftype を使う方法
use Scalar::Util qw(reftype);

say reftype $array_ref;  # ARRAY

# bless されたオブジェクトの場合
package Person;
my $obj = bless { name => 'Taro' }, 'Person';

say ref $obj;         # Person(クラス名)
say reftype $obj;     # HASH(実体の型)

多次元データ構造

リファレンスの真価は複雑なデータ構造を扱えることです。

二次元配列(行列)

 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
use v5.38;

# 3x3の行列
my $matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

# アクセス
say $matrix->[0][0];  # 1
say $matrix->[1][2];  # 6
say $matrix->[2][2];  # 9

# 行の取り出し
my $row = $matrix->[1];
say "@$row";  # 4 5 6

# ループで処理
for my $i (0 .. $#{$matrix}) {
    for my $j (0 .. $#{$matrix->[$i]}) {
        printf "%2d ", $matrix->[$i][$j];
    }
    say "";
}

# 出力:
#  1  2  3 
#  4  5  6 
#  7  8  9 

ハッシュの配列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use v5.38;

# ユーザーのリスト
my $users = [
    { id => 1, name => 'Alice', email => 'alice@example.com' },
    { id => 2, name => 'Bob',   email => 'bob@example.com' },
    { id => 3, name => 'Carol', email => 'carol@example.com' }
];

# アクセス
say $users->[0]{name};   # Alice
say $users->[1]{email};  # bob@example.com

# ループ
for my $user (@$users) {
    say "$user->{name} <$user->{email}>";
}

# 検索
my ($alice) = grep { $_->{name} eq 'Alice' } @$users;
say $alice->{id};  # 1

配列のハッシュ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use v5.38;

# グループ分けされたデータ
my $groups = {
    engineers => ['Alice', 'Bob', 'Charlie'],
    designers => ['Dave', 'Eve'],
    managers  => ['Frank']
};

# アクセス
say $groups->{engineers}[0];  # Alice
say $groups->{designers}[1];  # Eve

# ループ
for my $role (keys %$groups) {
    say "=== $role ===";
    for my $name (@{$groups->{$role}}) {
        say "  - $name";
    }
}

ハッシュのハッシュ

 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
use v5.38;

# 都市別の人口データ
my $population = {
    Tokyo => {
        population => 14_000_000,
        area       => 2194,
        prefecture => 'Tokyo'
    },
    Osaka => {
        population => 2_750_000,
        area       => 225,
        prefecture => 'Osaka'
    },
    Kyoto => {
        population => 1_460_000,
        area       => 828,
        prefecture => 'Kyoto'
    }
};

# アクセス
say $population->{Tokyo}{population};  # 14000000

# 人口密度を計算
for my $city (keys %$population) {
    my $data = $population->{$city};
    my $density = $data->{population} / $data->{area};
    printf "%s: %.0f people/km²\n", $city, $density;
}

深い入れ子構造

 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
42
43
use v5.38;

# 企業の組織図
my $org = {
    name => 'Example Corp',
    departments => {
        engineering => {
            manager => 'Alice',
            teams => {
                backend => {
                    lead    => 'Bob',
                    members => ['Charlie', 'Dave', 'Eve']
                },
                frontend => {
                    lead    => 'Frank',
                    members => ['Grace', 'Henry']
                }
            }
        },
        design => {
            manager => 'Iris',
            teams => {
                ui => {
                    lead    => 'Jack',
                    members => ['Kate', 'Leo']
                }
            }
        }
    }
};

# 深いアクセス
say $org->{departments}{engineering}{teams}{backend}{lead};
# Bob

# バックエンドチームのメンバー一覧
my $backend_members = 
    $org->{departments}{engineering}{teams}{backend}{members};
    
say "Backend team:";
for my $member (@$backend_members) {
    say "  - $member";
}

実用例

JSONライクなデータ構造

 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
use v5.38;
use JSON::PP;

# Perlのデータ構造
my $data = {
    user => {
        id       => 123,
        name     => 'Taro Yamada',
        email    => 'taro@example.com',
        verified => \1,  # JSON の true
        roles    => ['admin', 'editor']
    },
    posts => [
        {
            id      => 1,
            title   => 'First Post',
            tags    => ['perl', 'programming'],
            published => \1
        },
        {
            id      => 2,
            title   => 'Second Post',
            tags    => ['web', 'api'],
            published => \0  # JSON の false
        }
    ]
};

# JSONに変換
my $json = JSON::PP->new->pretty->encode($data);
say $json;

# JSONから復元
my $restored = JSON::PP->new->decode($json);
say $restored->{user}{name};  # Taro Yamada

設定ファイルの読み込み

 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
use v5.38;

# config.pl
my $config = {
    database => {
        host     => 'localhost',
        port     => 5432,
        name     => 'myapp',
        user     => 'dbuser',
        password => 'secret'
    },
    cache => {
        driver  => 'redis',
        host    => 'localhost',
        port    => 6379,
        timeout => 30
    },
    features => {
        new_ui       => 1,
        beta_api     => 0,
        experimental => 0
    }
};

# 使用例
sub connect_db {
    my $db_config = $config->{database};
    
    # DBI->connect(
    #     "dbi:Pg:host=$db_config->{host};port=$db_config->{port}",
    #     $db_config->{user},
    #     $db_config->{password}
    # );
}

# 機能フラグのチェック
if ($config->{features}{new_ui}) {
    say "Using new UI";
}

複雑なデータの変換

 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
use v5.38;

# CSVライクなデータを構造化
my @csv_data = (
    ['Name',    'Age', 'City'],
    ['Alice',   25,    'Tokyo'],
    ['Bob',     30,    'Osaka'],
    ['Charlie', 35,    'Kyoto']
);

# ハッシュの配列に変換
my @headers = @{shift @csv_data};
my @records;

for my $row (@csv_data) {
    my %record;
    for my $i (0 .. $#headers) {
        $record{$headers[$i]} = $row->[$i];
    }
    push @records, \%record;
}

# 使いやすい形式に
for my $record (@records) {
    say "$record->{Name} ($record->{Age}) lives in $record->{City}";
}

# グループ化
my %by_city;
for my $record (@records) {
    my $city = $record->{City};
    push @{$by_city{$city}}, $record;
}

# 都市ごとに表示
for my $city (sort keys %by_city) {
    say "=== $city ===";
    for my $person (@{$by_city{$city}}) {
        say "  $person->{Name}, $person->{Age}";
    }
}

サブルーチンリファレンスの活用

 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
42
use v5.38;

# コールバックパターン
sub process_list {
    my ($list_ref, $callback) = @_;
    
    my @results;
    for my $item (@$list_ref) {
        push @results, $callback->($item);
    }
    
    return \@results;
}

my $numbers = [1, 2, 3, 4, 5];

# 2倍にする
my $doubled = process_list($numbers, sub { $_[0] * 2 });
say "@$doubled";  # 2 4 6 8 10

# 平方する
my $squared = process_list($numbers, sub { $_[0] ** 2 });
say "@$squared";  # 1 4 9 16 25

# ディスパッチテーブル
my $operations = {
    add      => sub { $_[0] + $_[1] },
    subtract => sub { $_[0] - $_[1] },
    multiply => sub { $_[0] * $_[1] },
    divide   => sub { $_[0] / $_[1] }
};

sub calculate {
    my ($op, $a, $b) = @_;
    
    die "Unknown operation: $op" unless exists $operations->{$op};
    
    return $operations->{$op}->($a, $b);
}

say calculate('add', 10, 5);       # 15
say calculate('multiply', 10, 5);  # 50

リファレンスのリファレンス

リファレンス自体をリファレンスで参照することもできます。

 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
use v5.38;

my $value = 42;
my $ref1  = \$value;
my $ref2  = \$ref1;
my $ref3  = \$ref2;

# 多段デリファレンス
say $value;        # 42
say $$ref1;        # 42
say $$$ref2;       # 42
say $$$$ref3;      # 42

# 型の確認
say ref $ref1;     # SCALAR
say ref $ref2;     # REF
say ref $ref3;     # REF

# 実用例: 複雑なデータ構造への参照
my $complex = {
    data   => [1, 2, 3],
    nested => {
        deep => {
            value => 'found'
        }
    }
};

my $ref_to_complex = \$complex;
say $$ref_to_complex->{nested}{deep}{value};  # found

よくある間違いとデバッグ方法

間違い1: リファレンスと実体の混同

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use v5.38;

my @array = (1, 2, 3);
my $ref = \@array;

# 間違い: リファレンスを配列として扱う
# say "@$ref[0]";  # これは構文エラー

# 正しい
say $ref->[0];   # 1
say "@$ref";     # 1 2 3(配列全体)

# 間違い: 実体をリファレンスとして扱う
my %hash = (a => 1);
# say $hash->{a};  # エラー: %hash はリファレンスではない

# 正しい
say $hash{a};    # 1(ハッシュアクセス)

my $hash_ref = \%hash;
say $hash_ref->{a};  # 1(リファレンスアクセス)

間違い2: autovivification の誤解

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

my $data;

# これは自動的にハッシュリファレンスが作られる
$data->{user}{name} = 'Alice';

say ref $data;              # HASH
say $data->{user}{name};    # Alice

# 配列も同様
my $list;
$list->[0][1] = 'value';

say ref $list;              # ARRAY
say ref $list->[0];         # ARRAY
say $list->[0][1];          # value

# 無効化する場合
no autovivification;

my $test;
$test->{key} = 'value';  # エラーになる

間違い3: リファレンスのコピー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use v5.38;

my $original = [1, 2, 3];
my $copy = $original;  # これは浅いコピー(同じものを指す)

$copy->[0] = 999;

say $original->[0];  # 999(元も変わる!)

# 深いコピーが必要な場合
use Storable qw(dclone);

my $real_copy = dclone($original);
$real_copy->[1] = 888;

say $original->[1];   # 2(元は変わらない)
say $real_copy->[1];  # 888

Data::Dumper でデバッグ

 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
42
use v5.38;
use Data::Dumper;

my $complex = {
    users => [
        { id => 1, name => 'Alice', tags => ['admin', 'user'] },
        { id => 2, name => 'Bob',   tags => ['user'] }
    ],
    config => {
        version => '1.0',
        features => {
            new_ui => 1,
            beta   => 0
        }
    }
};

# 構造を見やすく出力
$Data::Dumper::Indent = 1;    # インデント
$Data::Dumper::Sortkeys = 1;  # キーをソート

say Dumper($complex);

# 出力:
# $VAR1 = {
#   'config' => {
#     'features' => {
#       'beta' => 0,
#       'new_ui' => 1
#     },
#     'version' => '1.0'
#   },
#   'users' => [
#     {
#       'id' => 1,
#       'name' => 'Alice',
#       'tags' => [
#         'admin',
#         'user'
#       ]
#     },
#     ...

Data::Printer でより見やすく

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use v5.38;
use DDP;  # Data::Printer

my $data = {
    array  => [1, 2, 3],
    hash   => { a => 1, b => 2 },
    nested => {
        deep => {
            value => 'here'
        }
    }
};

p $data;  # カラフルで見やすい出力

# 色なしで出力
p $data, colored => 0;

ref 関数でデバッグ

 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
use v5.38;

sub debug_ref {
    my ($ref, $name) = @_;
    
    $name //= 'value';
    
    if (!defined $ref) {
        say "$name is undefined";
    }
    elsif (!ref $ref) {
        say "$name is not a reference: $ref";
    }
    else {
        my $type = ref $ref;
        say "$name is a $type reference";
        
        if ($type eq 'ARRAY') {
            say "  Array has " . scalar(@$ref) . " elements";
        }
        elsif ($type eq 'HASH') {
            my @keys = keys %$ref;
            say "  Hash has " . scalar(@keys) . " keys: @keys";
        }
    }
}

my $array_ref = [1, 2, 3];
my $hash_ref = { a => 1, b => 2 };
my $scalar = "text";

debug_ref($array_ref, 'array_ref');
debug_ref($hash_ref, 'hash_ref');
debug_ref($scalar, 'scalar');
debug_ref(undef, 'undef_value');

循環参照に注意

リファレンスで自分自身を参照すると、メモリリークの原因になります。

 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
use v5.38;

# 危険な例
my $node = {
    value => 1,
    next  => undef
};

$node->{next} = $node;  # 自分自身を参照!

# この $node は永遠に解放されない可能性がある

# 解決策1: Scalar::Util::weaken を使う
use Scalar::Util qw(weaken);

my $parent = { name => 'parent', children => [] };
my $child  = { name => 'child', parent => $parent };

push @{$parent->{children}}, $child;
weaken($child->{parent});  # 弱い参照にする

# これでメモリリークを防げる

# 解決策2: 明示的に参照を切る
$node->{next} = undef;

パフォーマンスの考慮

リファレンス渡しで高速化

 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
use v5.38;
use Benchmark qw(cmpthese);

# 大きな配列
my @huge_array = (1 .. 10000);

# 値渡し(コピーが発生)
sub process_by_value {
    my (@array) = @_;
    my $sum = 0;
    $sum += $_ for @array;
    return $sum;
}

# リファレンス渡し(コピーなし)
sub process_by_ref {
    my ($array_ref) = @_;
    my $sum = 0;
    $sum += $_ for @$array_ref;
    return $sum;
}

# ベンチマーク
cmpthese(1000, {
    'by_value' => sub { process_by_value(@huge_array) },
    'by_ref'   => sub { process_by_ref(\@huge_array) }
});

# 結果: リファレンス渡しの方が圧倒的に速い

不要なコピーを避ける

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

my $data = {
    users => [
        { id => 1, name => 'Alice' },
        { id => 2, name => 'Bob' },
        # ... 1000件のデータ
    ]
};

# 遅い: 配列全体をコピー
sub get_user_names_slow {
    my ($data) = @_;
    my @users = @{$data->{users}};  # コピーが発生
    return map { $_->{name} } @users;
}

# 速い: リファレンスのまま処理
sub get_user_names_fast {
    my ($data) = @_;
    return map { $_->{name} } @{$data->{users}};
}

まとめ

Perlのリファレンスは、複雑なデータ構造を扱うための強力な機能です。この記事で学んだ内容を整理しましょう。

重要ポイント

  1. リファレンスの作成

    • \ 演算子: 既存の変数のリファレンス
    • []: 無名配列リファレンス
    • {}: 無名ハッシュリファレンス
    • sub {}: 無名サブルーチンリファレンス
  2. デリファレンス

    • 矢印演算子 -> が最も一般的
    • @{}, %{}, ${} による全体アクセス
    • ポストフィックス記法(v5.20+): ->@*, ->%*
  3. 多次元データ構造

    • 配列の配列、ハッシュのハッシュなど自由に構築可能
    • 矢印は連続する場合に省略可能: $data->[0]->[1]$data->[0][1]
  4. デバッグ

    • Data::Dumper で構造を可視化
    • ref 関数で型を確認
    • Scalar::Util::reftype でblessされたオブジェクトの実体を確認
  5. 注意点

    • 循環参照によるメモリリーク
    • 浅いコピーと深いコピーの違い
    • リファレンス渡しによるパフォーマンス向上

次のステップ

リファレンスを理解したら、次は以下のトピックに進むとよいでしょう:

  • オブジェクト指向Perl: bless とクラス設計
  • Moo/Moose: モダンなOOPフレームワーク
  • 高度なデータ構造: タイ変数、弱参照、内部構造
  • XS: C言語との連携とリファレンスの内部実装

リファレンスは最初は難しく感じるかもしれませんが、使いこなせるようになれば、Perlの真の力を引き出せます。たくさんコードを書いて、Data::Dumperで構造を確認しながら、実践的に学んでいきましょう!

Perl Advent Calendar 2025、明日の記事もお楽しみに!

参考リンク

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