Perl Hackers Hub

第7回新人さんのための仕事で使えるPerl基礎知識(3)

リファレンス/デリファレンス

(3)では、仕事でPerlを使ううえで必ずと言っていいほど使用するにもかかわらず、つまずきがちなリファレンスとデリファレンスについて解説します。筆者も最初に教えてもらった際にはちゃんと理解できていませんでしたが、使っていくうちに徐々に体系的に理解できるようになりました。

リファレンス

配列やハッシュといった変数をサブルーチンへの引数としてそのまま使うと、少々困ったことになってしまいます。たとえば次のコードを書いたとします。

sub foo {
    my(@arg_a, @arg_b) = @_;
}
my @a = (1, 2, 3);
my @b = (4, 5, 6);
foo(@a, @b);

こういったコードを書いたときに、サブルーチンfooの引数に渡される値として、@arg_aには@aの内容が、@arg_bには@bの内容が入ることが期待されますが、実際には@arg_aの中に@a@bの内容が入ってしまいます。これは、

@_ = (@a, @b);

という擬似コードのような形で、2つの配列が1つの配列としてまとめられてしまうため、結果的に@arg_a@_のすべての内容が渡されます。

そこでリファレンスの出番です。リファレンスは値を記録する代わりに、値を格納しているアドレスをメモリ上に記録します。C言語の&演算子を使ってアドレスを得るものと同じような働きをします。

リファレンスは、変数名の前にバックスラッシュ演算子(/)を付けることで作成します。先ほどのコードをリファレンスを使って書くと次のようになります。

sub foo {
    my($arg_a_ref, $arg_b_ref) = @_;
}
my @a = (1, 2, 3);
my @b = (4, 5, 6);
my $a_ref = \@_;
my $b_ref = \@_;
foo($a_ref, $b_ref);

このように、リファレンスにすると配列のアドレスを表すスカラ変数として処理されるため、実質的に複数の配列を引数として渡しても、それぞれの配列の内容を別々に受け取ることができるようになります。

デリファレンス

このようにして作成したリファレンスが指している実体の値にアクセスすることを、デリファレンスと言います。

デリファレンスするときは、取得したい値の型に合わせて、$、@、%をリファレンスを格納している変数の頭に付けます。アロー->演算子を使い、わかりやすく表現できます。先ほどのコードをもとにすると次のように書くことができます。

sub foo {
    my($arg_a_ref, $arg_b_ref) = @_;
    print $arg_a_ref->[0]; # 1
    print $arg_b_ref->[0]; # 4
}

以降では、リファレンス/デリファレンスの使い方の詳細を見ていきます。

配列

リファレンス

配列のリファレンスを作成する場合、配列の変数にバックスラッシュ\を付けます。

my @array = qw/a b c d e/;
my $array_ref = \@array;

上記のようにわざわざバックスラッシュをつけてリファレンスを取得しなくても、直接的に無名配列のリファレンスを作ることができます。無名配列の場合はスカラに対して[]を使います。

$array_ref = [1, 2, ['a', 'b', 'c']];

デリファレンス

リファレンスにはあくまでもメモリ上の場所が格納されているため、実際に取り出す際にはデリファレンスすることで実体の値にアクセスします。デリファレンスするにはリファレンスの変数名の先頭に@を付けて配列全体として参照するか、アロー演算子を付けて配列の要素数を指定し配列リファレンスの一部を参照します。

my $array_ref = ["blue", "red", "black"];

print "My first color is:" . $array_ref->[0] . "\n";
# My first color is: blue

デリファレンスの際の書き方は3種類あり、次の例はどれも同じ結果になります。

print $array_ref->[0]; # blue
print ${$array_ref}[0]; # blue
print $$array_ref[0]; # blue

@演算子を使ったデリファレンス

リファレンスを作成し、デリファレンスして実体にアクセスするコードの全体は次のようになります。

my $array_ref = ["blue", "red", "black"];
foreach my $color ( @{$array_ref} ){
    print "color is $color \n";
}

実行結果は次のようになります。

color is blue
color is red
color is black

リファレンスの変数をそのまま表示する

配列のリファレンスをデリファレンスせずに表示するコードは次のようになります。

print "Reference: " . $array_ref . "\n";

実行結果は次のようになります。

Reference: ARRAY(0x1008d0040)

ARRAY(16進数)16進数部分はメモリ上の場所を意味しています。ARRAYはリファレンスの型が配列だということを表しています。もし後述するハッシュのリファレンスだった場合にはARRAYの部分がHASHになります。

ハッシュ

リファレンス

ハッシュのリファレンスも配列のリファレンスと同様に、変数に対してバックスラッシュを付けます。

my %hash = (
    'Blue' => '#0000FF',
    'Red' => '#FF0000',
);
my $hash_ref = \%hash;

無名ハッシュの場合は{}を使います。

$hash_ref = {
    'name' => 'Dog',
    'age' => 2,
};

デリファレンス

デリファレンスするにはリファレンスの変数名の先頭に%を付けてハッシュ全体として参照するか、アロー演算子を付けてハッシュのkeyを指定しハッシュリファレンスの一部を参照します。

my $hash_ref = {'name' => 'Hideo', 'age' => '40'};

print "My name is $hash_ref->{name}. \n";
print "I am $hash_ref->{age} years old. \n";
# My name is Hideo.
# I am 40 years old.

ハッシュのデリファレンスの際の書き方も3種類あり、次の例はどれも同じ結果になります。

print ${$hash_ref}{name}; # Hideo
print $$hash_ref{name}; # Hideo
print $hash_ref->{name}; # Hideo

%演算子を使ったデリファレンス

ハッシュの場合の全体像は次のようになります。

my $hash_ref = {'name' => 'Hideo', 'age' => '40'};

while ( ($key, $value) = each(%$hash_ref) ) {
    print "key: $key \n";
    print "value: $value \n";
}

実行結果は次のようになります。

key: name
value: Hideo
key: age
value: 40

サブルーチン

サブルーチンのリファレンスは次のように作ることができます。一般的に「コードリファレンス」と呼びます。

$foo = sub { print "hello!\n" };
$foo->(); # hello!

これは、ほかの言語でも使われるクロージャと呼ばれる使い方ができます。

my $foo = 'pen';
my $code = sub {
    print "This is a $foo"; # This is a pen
};

複雑なデータ構造の取り扱い

仕事で扱うデータ構造では、今まで挙げたような簡単なリファレンスが出てくることはありません。ぱっと見では何かわからない、複雑なデータ構造を扱わなければなりません。

ここでは、これから仕事上で使うかもしれない4種類のデータ構造を紹介します。これまでのリファレンスの内容を理解していれば、組み合わせるだけなので想像している以上に簡単です。

配列の中に配列リファレンスを入れる

配列の中に配列のリファレンスを入れることができます。

my @AoA = (
        [ "cat", "dog", "koala" ],
        [ "camel", "llama", "owl" ],
);
my $array_ref = \@AoA;

配列の中の配列のリファレンスにアクセスするための2つめ以降のアロー演算子は省略できます。

print $array_ref->[1]->[0]; # camel
print $array_ref->[1][0]; # camel

配列の中にハッシュのリファレンスを入れる

配列の中にハッシュのリファレンスを入れることができます。

my @AoH = (
    {
        name => "cat",
        age => 10,
    },
    {
        name => "dog",
        age => 5,
    },
  );

my $array_ref = \@AoH;

先と同様に、2つめ以降のアロー演算子は省略できます。

print $array_ref->[0]->{name}; # cat
print $array_ref->[0]{name}; # cat

配列のハッシュにアクセス

ハッシュのvalue要素に、配列のリファレンスを入れることもできます。

my %HoA = (
    animals1 => [ "cat", "dog", "koala" ],
    animals2 => [ "camel", "llama", "owl" ],
);

my $hash_ref = \%HoA;

print $hash_ref->{animals1}->[2]; # koala
print $hash_ref->{animals2}[1]; # llama

ハッシュのハッシュにアクセス

ハッシュのvalue要素に、ハッシュのリファレンスを入れることもできます。

my %HoH = (
    cat => {
        count => 10,
    },
    dog => {
        count => 5,
    },
);

my $hash_ref = \%HoH;

print $hash_ref->{cat}->{count}; # 10
print $hash_ref->{dog}{count}; # 5

リファレンスの型を調べる

リファレンスの型が何であるかを調べるには、Perlの標準関数refを使います。リファレンスの型が文字列(ARRAYやHASHなど)で返ります。次のようにif文など条件分岐させて使うことが多いです。

my $man = {'name' => 'Hideo', 'age' => '40'};

if ( ref($man) eq 'HASH' ){
    print "man is HASH";
}

Perl 5.14からデリファレンスが変わる!!

今後リリース予定のPerl 5.14以降では、配列やハッシュを受け取る標準関数に対し、配列リファレンスやハッシュリファレンスを直接渡すことができるようになります。

今までは、

my $array_ref = [1, 2, 3, 4];
push @{ $array_ref }, 5;

と書かなければなりませんでしたが、Perl 5.14からは、

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

と書けるようになり、リファレンスの扱いが非常に簡潔になります。これは革新的なアップデートであり、今まで{}を付けるかどうか悩んでいた人には朗報でもあります。

また、keysやvalue、eachなども同様に、%{}@{}を付けて明示的に何の型のリファレンスであるかを表現しなくてもよくなります。

for ( keys %{$hoh->{hok}} ) {...} # Perl 5.12以前
for ( keys $hoh->{hok} ) {...} # Perl 5.14以降

本稿執筆中には、正式版のPerl 5.14はまだリリースされていませんが、開発版のPerl 5.13.xをインストールすることによって試すことができます

リファレンスは怖くない

本節では、リファレンス/デリファレンスや複雑なデータの扱い、今後のPerlでのデリファレンスの方法を説明しました。CPANにもたくさんのコードがあるため、実際にコードを書いて少しずつ実践して慣れることが、何よりも学ぶ機会となるでしょう。

Perlにはperldocと呼ばれるたくさんの公式ドキュメントがあり、有志によって日本語訳されているマニュアルがあります。基本の説明やチュートリアル、FAQなどの内容も充実していますので、ぜひ目を通してください。

Perlのリファレンスに関する概要ドキュメント
http://perldoc.jp/docs/perl/5.10.0/perlref.pod
Perlのリファレンスに関するチュートリアル
http://perldoc.jp/docs/perl/5.10.0/perlreftut.pod

おわりに

今回は「新人さんのための仕事で使えるPerl基礎知識」というテーマでしたが、いかがでしたでしょうか。本稿により、つまずきがちな点が少しでもクリアになっていただけるとうれしく思います。

次回の執筆者は広木大地さんで、テーマは「多人数でのチーム開発技法」を予定しています。お楽しみに。

おすすめ記事

記事・ニュース一覧