これは Perl 入学式 Advent Calendar の 19日目の記事です。

こんにちは。東京のPerl入学式でサポーターをしている@tsucchiです。

昨日は papix さんのPerl入学式の歴史 〜その3 2013年〜でした。「ただいま作成中」しか書いてない気がしますが、 きっと気のせいでしょう。

本日は毛色を変えて、リファレンスの話をしようかと思います。

はじめに

さてさて、Perl入学式では第3回から「リファレンス」の使い方を学習しています。リファレンスは最大の山場の一つで、かくいう僕もきちんと理解して使える様になるまでには 結構時間がかかった記憶があります。校長も「難しい!」「たくさん書いて慣れるしかない!」と、良く言ってますよね。

リファレンスの文法的なものは、Perl 入学式のスライドや、入門書をご覧いただくとして、 ここでは「リファレンスって何に使うの?なんでリファレンスを使うと嬉しいの?」みたいな話をしようと思います。

リファレンスの使い道

リファレンスの使い道は大きく分けて 3 つくらいだと僕は考えています。

  1. 複数の配列を関数やメソッドに渡す
  2. 複雑なデータ構造を作る
  3. 関数やメソッドで引数を更新する

このうち、最初の1つだけは Perl 固有の事情で、残りは他の言語でも当てはまるリファレンス(やポインタなどの参照系のデータ構造)に当てはまる特長です。 では順番に見ていきましょう。

複数の配列をサブルーチンに渡す

サブルーチンをちゃんと作れるようになったぶつかる難問(?)の一つが、「複数の配列を引数にとるサブルーチン」です。たとえば、下記みたいな、「二つの配列を引数にとり、配列の中身が同じかどうかを 比較するサブルーチン: is_same_array()」を作るとします。

Perl の引数の制約がなければ(あるいは知らなければ)こういう風に書きたい(書いちゃう)ですよね?

# @array1 と @array2 が同じなら真を返す
sub is_same_array {
    my (@array1, @array2) = @_;
    ...
}

ですが、これはうまく動きません。たとえば、下記のようなコードを書いたとすると…

my @a1 = (1, 2, 3);
my @a2 = (1, 2, 3);
if ( is_same_array(@a1, @a2) ) {
    print "配列の中身は同じ\n";
}
else {
    print "配列の中身は違う\n";
}

「配列の中身は違う」の方が出力されるはずです。 Perl のサブルーチンの引数は配列として取り扱うため、is_same_array の第一引数@array1(1, 2, 3, 1, 2, 3)のように2つの配列をくっつけた値が入ってしまうのです。

こういう場合は配列リファレンスを渡すようにします。

# $array_ref1 と $array_ref2 が同じなら真を返す
sub is_same_array {
    my ($array_ref1, $array_ref2) = @_;
    ...
}
my @a1 = (1, 2, 3);
my @a2 = (1, 2, 3);
if ( is_same_array(\@a1, \@a2) ) { # 配列ではなく、配列リファレンスを渡す
    print "配列の中身は同じ\n";
}
else {
    print "配列の中身は違う\n";
}

今度は「配列の中身は同じ」が出力されるはずです。(is_same_arrayをちゃんと実装すれば)

複雑なデータ構造を作る

複雑なデータ構造を作るために、リファレンスはよく使われています。

Perl入学式のスライドでは2次元配列を例に説明しているので、 ここでは別の例を出してみましょう。

下記みたいなデータがあるとします。DB とか CSV とか Excel とか HTML のテーブルとかでよく見る構造ですよね?

id name age
1 Sherlock Shellingford 15
2 Nero Yuzurizaki 15
3 Hercule Barton 16
4 Cordelia Glauca 17

このデータの1行分はどういうデータ構造をつかうのが自然でしょうか?配列でもいいですが、ハッシュの方がわかりやすいので、ハッシュがいいですね。

こんな感じです。

my %row = (
    id   => 1,
    name => 'Sherlock Shellingford',
    age  => 15,
);
print $row{name}; # => Sherlock Shellingford

では、このデータ全部は、どのような構造がいいでしょうか?「配列で中身がハッシュ」(?)

配列は配列、ハッシュはハッシュなので、そのまま共存させることはできません。

ここで必要になってくるのがリファレンスです。ハッシュリファレンスであれば、配列に入れることができます。こんな感じです。

my @rows = (
    {
        id   => 1,
        name => 'Sherlock Shellingford',
        age  => 15,
    },
    {
        id   => 2,
        name => 'Nero Yuzurizaki',
        age  => 15,
    },
    {
        id   => 3,
        name => 'Hercule Barton',
        age  => 16,
    },
    {
        id   => 4,
        name => 'Cordelia Glauca',
        age  => 17,
    },
);
print $row[0]->{name}; # => Sherlock Shellingford

このように、配列の中にハッシュが入っているような構造や、配列の中に配列が入っているような構造や、ハッシュの中に配列が入るような構造を 作りたい場合にリファレンスを使います。(もっと複雑なのも作れますが、あまり複雑すぎるのも良くないので、よく考えて使いましょうね!)

関数やメソッドで引数を更新する

たとえば以下のような配列があるとします。

my @people = (
    'tsucchi',
    'xtetsuji',
    'papix',
    'ytnobody',
    ...
);

で、この配列の中身をすべて小文字->大文字に変換する、to_upper_items という関数を作りたいとします。

sub to_upper_items {
    my (@array) = @_;
    for my $item ( @array ) {
        $item = uc($item);
    }
}
to_upper_items(@people); #これで中身が全部大文字になってほしいけどNG

これはうまく動きません。関数でなければ大丈夫ですが、関数だとダメなのです。なぜかというと、関数の引数は渡ってきた値をコピーします。なので、関数に渡した @people と 関数内で使っている @array はコピーされた別物なのです。これは、関数内で不用意に書き換えるミスを防げるので一般的には望ましいことなのですが、今作りたい関数にとっては 都合が悪いです。

この場合もリファレンスを使います。リファレンスはコピーされるのはアドレスなので、関数内の実体は渡したものと同じになります。(この辺の仕組みはリファレンスをちゃんと理解しないと ピンとこないと思いますが…)

sub to_upper_items {
    my ($array_ref) = @_;
    for my $item ( @{ $array_ref } ) {
        $item = uc($item);
    }
}
to_upper_items(\@people); #リファレンスを渡せば大丈夫

このようにリファレンスを渡すと、配列の中身を書き換えることができます。これはPerl入学式のスライドでは、 「リファレンスのはまりどころ」として紹介しているのですが、意図してやりたい場合もありますよ、というお話でした。

まとめ

他にもリファレンスの便利な使用例はいくつかあるのかもしれませんが、ぱっと思いついたものを書いてみました。リファレンスは慣れるまではなかなか大変なのですが、便利なもの であることは間違いないので、使いこなせるようになるといいですね!

明日は akms さんです。お楽しみに!