perlreftut - Mark によるリファレンスに関するとても短いチュートリアル
Perl 5 における最も重要な新機能の一つは、多次元配列やネストした ハッシュのような複雑なデータ構造を扱うことのできる能力です。 これらを可能とするために、Perl 5 は「リファレンス」と呼ばれる機能を導入し、 そしてリファレンスを使うことは、複雑で構造化されたデータをPerlで扱うことの 鍵です。 残念なことに、学ぶにはおかしな構文がたくさんあり、メインの マニュアルページはフォローするのが難しい状態です。 マニュアルはほぼ完璧で、ときとして読者は何が重要で何が重要でないかを 説明するのが難しいので問題を見つけることがあります。
幸運にも、メインページにあることの 10% を知るだけで 90% の恩恵を 受けることができます。 このページではあなたにその 10% をお見せします。
Perl 4 の時代にあった問題の一つが、リストの値を持ったハッシュの表現を どのように行うかということでした。 Perl 4 はもちろんハッシュを持っていましたが、その値は スカラでなければならず、リストを使うことはできませんでした。
リストのハッシュをなぜ使いたいのでしょうか? 簡単な例で考えてみましょう: あなたが以下のような都市と国の名前のファイルを持っていたとします:
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
そして、以下のように、国は一度だけ現れてその国の都市がアルファベット順に 現れるような出力を得たかったとします:
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.
これを行う自然な方法は、キーが国の名前であるハッシュを使うことです。 国の名前はその国の都市のリストに関連付けられます。 入力を読むたびに国と都市に分割し、新たな都市をリストに追加します。 入力を読み終えたら通常通りハッシュをイテレートして、出力の前に都市の 各リストをソートしてやります。
もしハッシュの値がリストにできなければあなたの負けです。 Perl 4 では、ハッシュの値はリストにはできず、文字列だけが可能でした。 おそらくはすべての都市を一つの文字列に連結し、出力するときにその文字列を リストに分解してからそのリストをソートして、その結果を再度文字列へ戻す 必要があるでしょう。 これはわかりにくくて、エラーを持ち込みやすいやり方です。 ハッシュの値をリストにできさえすれば、問題を解決できる完璧なリストを すでに Perl は持っているので、これは不満がたまります。
Perl 5 の時代でも、すでにこのデザインに困っていました: ハッシュの値は スカラでなければならないのです。 これを解決するのがリファレンスです。
リファレンスは配列全体やハッシュ全体(もしくはそれ以外の何か)を 参照する スカラです。 名前はすでになじみの深いリファレンスの一種です。 アメリカ合衆国の大統領を考えてみましょう: 厄介で不自由な、血や骨の 入った袋です。 しかし、彼について語るときやコンピュータプログラムで彼を表すのために 必要なのは、簡単で、便利なスカラ文字列「Barack Obama」なのです。
Perl におけるリファレンスは配列やハッシュの名前に似ています。 それらは Perl のプライベートで内部的なな名前なので曖昧さがないことを 保証できます。 「Barack Obama」とは異なり、一つのリファレンスは一つのものしか参照しません。 配列全体を一つの名前でリカバーできます。 ハッシュへのリファレンスを持っていれば、ハッシュ全体をリカバーできます。 しかし、リファレンスは簡単で、コンパクトなスカラ値なのです。
あなたは値が配列であるハッシュを持つことはできません。 ハッシュの値はスカラのみ可能です。 わたしたちはそれに困っています。 しかし、一つのリファレンスは配列全体を参照することができ、リファレンスは スカラなので、配列へのリファレンスのハッシュを持つことができます。 そしてそれは配列のハッシュのように振る舞い、配列のハッシュであるかのように 便利なのです。
この都市と国の問題にはリファレンスを扱うための幾つかの構文を見た後で 戻ります。
リファレンスを作るには二つの方法があり、使うにも二つの方法があります。
ある変数の先頭に \
をつければ、その変数へのリファレンスを 得ることができます。
$aref = \@array; # $aref は @array へのリファレンスを保持する
$href = \%hash; # $href は %hash へのリファレンスを保持する
$sref = \$scalar; # $sref は $scalar へのリファレンスを保持する
$aref や $href のような変数にリファレンスを格納してしまえば、 スカラ変数のようにコピーしたり格納することができます:
$xy = $aref; # $xy は @array へのリファレンスを保持する
$p[3] = $href; # $p[3] は %hash へのリファレンスを保持する
$z = $p[3]; # $z は %hash へのリファレンスを保持する
これらの例は、名前を使って変数へのリファレンスを作る方法を 例示するものでした。 ときとして、名前を持っていない配列やハッシュを作りたいときが あるかもしれません。 これは、文字列 "\n"
や、数値 80 を、一旦名前付き変数に保管する 必要なしに使えるようにする方法と似ています。
Make Rule 2
[ ITEMS ]
は新たな無名配列を作り、その配列へのリファレンスを返します。 { ITEMS }
は新たな無名ハッシュを作り、そのハッシュへのリファレンスを 返します。
$aref = [ 1, "foo", undef, 13 ];
# $aref は配列へのリファレンスを保持している
$href = { APR => 4, AUG => 8 };
# $href はハッシュへのリファレンスを保持している
ルール 2 によって得たリファレンスはルール 1 によって得た同種の リファレンスと同じです:
# これは:
$aref = [ 1, 2, 3 ];
# これと同じ:
@array = (1, 2, 3);
$aref = \@array;
最初の行は続く二行を短くしたもので、@array
という余分な配列変数を 作りません。
[]
と書いた場合には新たな空の無名配列が得られます。 {}
と書いた場合には新たな空の無名ハッシュが得られます。
リファレンスを得た後でそれに対してできることは? リファレンスはスカラ値であり、スカラであるかのように格納したり 値を得たりできることを見てきました。 リファレンスを使うには他に二つの方法があります。
配列のリファレンスを、配列の名前が置かれる場所でカーリーブレースの中で 使うことができます。 たとえば、@array
の代わりに @{$aref}
とします。
以下に例を挙げます:
配列:
@a @{$aref} 配列
reverse @a reverse @{$aref} 配列を反転する
$a[3] ${$aref}[3] 配列の要素
$a[3] = 17; ${$aref}[3] = 17 要素の代入
各行の二つの式は同じことを行います。 左側のものは @a
という配列に対する操作で、右側のものは $aref
によって 参照される配列に対する操作です。 操作される配列を見つければ、両方のバージョンは配列に対して同じことを 行います。
ハッシュのリファレンスを使うことも まったく 同じです:
%h %{$href} ハッシュ
keys %h keys %{$href} ハッシュからキーを得る
$h{'red'} ${$href}{'red'} ハッシュの要素
$h{'red'} = 17 ${$href}{'red'} = 17 要素への代入
リファレンスに対して行いたいことはすべて、"Use Rule 1" で どのように行うかが説明されています。 通常の配列やハッシュに対して同じことを行うような Perl コードを書き、その 配列やハッシュをリファレンス {$reference}
で置き換えるのです。 「私が持っているのがリファレンスであるとき、配列に対してループするには?」 そう、配列に対してループするには次のように書くでしょう
for my $element (@array) {
...
}
そしてこの配列名 @array
をリファレンスで置き換えます:
for my $element (@{$aref}) {
...
}
「私が持っているのがリファレンスであるとき、ハッシュの内容を出力するには?」 まずはじめにハッシュを出力するコードを書きます:
for my $key (keys %hash) {
print "$key => $hash{$key}\n";
}
そしてハッシュの名前をリファレンスで置き換えます:
for my $key (keys %{$href}) {
print "$key => ${$href}{$key}\n";
}
"Use Rule 1" はあなたが実際に必要とするすべてです。 なぜなら、リファレンスについて必要となることすべてを説明しているからです。 しかし、配列やハッシュについて行いたいことの大部分は一つの要素を 取り出すことで、"Use Rule 1" の記法は扱いにくいものです。 そのため、略記法があります。
${$aref}[3]
は読みづらいので、代わりに $aref->[3]
と書くことが できます。
${$href}{red}
は読みづらいので、代わりに $href->{red}
と 書くことができます。
$aref
が配列へのリファレンスを保持しているとき、$aref->[3]
は その配列の四番目の要素です。 これと $aref[3]
を混同しないでください。 後者は @aref
という名前のついた配列の四番目の要素です。 $aref
と @aref
は、$item
と @item
がそうであるように 無関係なものです。
同様に、$href->{'red'}
はスカラ変数 $href
によって参照される ハッシュ(おそらくは名前のないもの)の一部分です。 $href{'red'}
は %href
という名前のついたハッシュの一部です。 ->
はつけ忘れやすく、もしつけ忘れたならばあなたのプログラムが配列や ハッシュの要素を取り出そうとしたときに、予期していないハッシュや配列を アクセスしたことによる奇妙な結果を得ることになるでしょう。
これがどんなに便利なことかを例を挙げてみてみましょう。
まずはじめに、[1, 2, 3]
が (1, 2, 3)
から構成される無名配列を 作り出し、その配列に対するリファレンスを与えることを思い出してください。
ここで以下について考えます
@a = ( [1, 2, 3],
[4, 5, 6],
[7, 8, 9]
);
@aは三つの要素をもつ配列で、その要素はそれぞれ別の配列に対する リファレンスです。
$a[1]
はそのようなリファレンスの一つです。 これは (4,5,6)
からなる配列を参照します。 これは配列へのリファレンスで、"Use Rule 2" はそのような配列の第三要素を 得るために $a[1]->[2]
と書けることを述べていたので、 $a[1]->[2]
は 6 になります。 同様に、$a[0]->[1]
は 2 です。 ここで私たちが得たものは二次元配列のようなものです; 配列の任意の行の任意の列にある要素を得たり、それにセットしたりするのに $a[ROW]->[COLUMN]
と書くことができます。
この記法はまだ少々扱いにくいものなので、略記法があります:
矢印は、二つの 添え字 の間にあるのなら、省略できます。
$a[1]->[2]
は $a[1][2]
と書くことができます; これらは同じことを意味します。 $a[0]->[1] = 23
と書く代わりに $a[0][1] = 23
とできます。 これらは同じことです。
これで本当に二次元配列らしくなりました!
矢印が重要なことがこれでわかります。 もし矢印がなければ、$a[1][2]
の代わりに ${$a[1]}[2]
と 書かなければなりません。 三次元配列では、${${$x[2]}[3]}[5]
のような読みづらいものではなくて $x[2][3][5]
とできます。
以下は先に保留していた問題に対する解答です。 都市と国の名前のファイルの再フォーマットを行うものです。
1 my %table;
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
8 foreach $country (sort keys %table) {
9 print "$country: ";
10 my @cities = @{$table{$country}};
11 print join ', ', sort @cities;
12 print ".\n";
13 }
プログラムは二つの部分から構成されています: 2 行目から 7 行目は入力を 読み込んでデータ構造を構築します。 そして 8 行目から 13 行目でデータを解析して結果を出力します。 わたしたちはここで、キーとして国の名前を持ち、値として都市名のリストへの リファレンスを持つハッシュ %table
を作ろうとしています。 データ構造は以下のようなものです:
%table
+-------+---+
| | | +-----------+--------+
|Germany| *---->| Frankfurt | Berlin |
| | | +-----------+--------+
+-------+---+
| | | +----------+
|Finland| *---->| Helsinki |
| | | +----------+
+-------+---+
| | | +---------+------------+----------+
| USA | *---->| Chicago | Washington | New York |
| | | +---------+------------+----------+
+-------+---+
最初に出力を見ましょう。 ここで、すでに上記の構造ができているとします。 どのように出力するのでしょうか?
8 foreach $country (sort keys %table) {
9 print "$country: ";
10 my @cities = @{$table{$country}};
11 print join ', ', sort @cities;
12 print ".\n";
13 }
%table
は通常のハッシュで、そこからキーのリストを得てそれをソートして 通常通りキーに対してループします。 リファレンスは 10 行目でだけ使われています。 $table{$country}
はハッシュの $country
キーを参照します。 これはその国の都市の配列に対するリファレンスです。 "Use Rule 1" は配列を @{$table{$country}}
で取り出せるといっています。 10行目は
@cities = @array;
と同じようなものですが、array
という名前が {$table{$country}}
という リファレンスに置き換えられています。 @
は Perl に配列全体を取り出すことを指示しています。 都市のリストを得たらそれをソートして、つなげ、そして通常と同じように 出力します。
2 行目から 7 行目は構造を構築している部分です。 再掲します:
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
2 行目から 4 行目は都市と国の名前を得ています。 5p行目はその国がすでにハッシュのキーとして存在しているかどうかを見ています。 もし存在していなければ、プログラムは[]記法(Make Rule 2)を使って新しい 空の都市が格納される無名配列を作り出します。 そして、リファレンスを配列の適切なキーにセットします。
6行目は都市名を対応する配列にインストールします。 $table{$country}
はここでその国の都市の配列に対するリファレンスを 保持しています。 6 行目は
push @array, $city;
のようなものですが、異なるのは array
が {$table{$country}}
という リファレンスに置き換わっている点です。 push
は都市名を参照されている配列の末尾に追加します。
スキップした点があります。 5 行目は不必要なので、取り除くことができます。
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 #### $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
%table
の中に現在の $country
のためのエントリがすでに存在していれば 異なる点はありません。 6 行目は配列へのリファレンスである $table{$country}
の値に注目し、 その配列に $city
をプッシュします。 しかし、$country
が %table
の中にない Greece
のようなキーを 保持していたら何をするのでしょうか?
これは Perl です; ですから、本当に正しいことを行います。 存在していない配列に Athens をプッシュしようとするので、新しく空の 無名配列をあなたのために作り出してそれを %table
にインストールします。 そしてそれから Athens
をそこにプッシュします。 これは `autovivification' と呼ばれます。 Perl はハッシュの中にそれらのキーが存在しないことを確認し、新しいハッシュ エントリを自動的に作り出します。 Perl はあなたがハッシュの値を配列として扱いたがっていることを 知っているので、新しい空の配列を作り出してハッシュの中にそれに対する リファレンスを自動的にインストールします。 いつもと同じように、Perlは新たな都市名を保持する一要素の配列を 作り出します。
わたしはあなたに 10% の詳細で 90% の利益を得ることを約束しました。 そしてそれは詳細の 90% をそのままにしているということです。 今、あなたは重要な部分を見てきました。 それにより詳細の100% を述べている perlref man ページをより簡単に 読むことができるようになったでしょう。
perlref のハイライトの幾つかを挙げておきます:
任意のものに対するリファレンスを作成することができます。 そこにはスカラ、関数、他のリファレンスも含まれます。
"Use Rule 1" の中で、その中にあるものが $aref
のようなアトミックな スカラ変数である場合にはカーリーブラケットを省略することができます。 たとえば、@$aref
は @{$aref}
と同じで、$$aref[1]
は ${$aref}[1]
と同じです。 始めたばかりなのであれば、常にカーリーブラケットで囲むことを 習慣付けたくなるかもしれません。
以下は配列の内容をコピーしません:
$aref2 = $aref1;
同じ配列に対する二つのリファレンスが得られます。 もし $aref1->[23]
を変更して、$aref2->[23]
を 参照したならば変更したものが見えるでしょう。
配列をコピーするには以下のようにします
$aref2 = [@{$aref1}];
これは新たな無名配列を作り出すために [...]
記法を使っています。 そして、$aref2
は新たな配列に対するリファレンスが代入されます。 新たな配列は $aref1
によって参照される配列の内容によって初期化されます。
同様に、無名ハッシュをコピーするには以下のようにします
$href2 = {%{$href1}};
配列がリファレンスを保持しているときにそれを確認するには、ref
関数を 使います。 この関数はその引数がリファレンスであるときには真を返します。 実際にはもうちょっと良くて、ハッシュリファレンスであれば HASH
を、 配列リファレンスであれば ARRAY
を返します。
リファレンスを文字列のように使った場合には、以下のような文字列が得られます
ARRAY(0x80f5dec) or HASH(0x826afc0)
もしこのような文字列を見たならば、リファレンスを間違って出力したことを 知ることとなるでしょう。
この表現の副作用は、eq
を二つのリファレンスが同じものを 参照しているかどうかを確認するために使うことができるということです (しかし、通常はより早い ==
を代わりに使うべきでしょう)。
文字列をリファレンスであるかのように使うことができます。 文字列 "foo"
を配列リファレンスとして使うとき、@foo
への 参照であるかのように受け付けられます。 これは ソフトリファレンス または シンボリックリファレンス と 呼ばれます。 use strict 'refs'
と宣言することによって、アクシデントによって問題が 引き起こされる場合があるこの機能を禁止することができます。
perlref よりも perllol に行きたいと思うかもしれません。 そこではリストのリストや多次元配列について詳しく述べられています。 その後で、perldsc に行くと良いでしょう。 これはデータ構造クックブック(Data Structure Cookbook)で、ハッシュの配列、 配列のハッシュ、その他のデータの使用や出力についてのレシピがあります。
すべての人が複合データ構造を必要としていて、Perlでのそれを得るやり方は リファレンスです。 リファレンスを扱うにあたって四つの重要なルールがあります: 二つは リファレンスの作成についてで、二つはリファレンスの使用についてです。 これらのルールを知ってしまえば、あなたがリファレンスを使って行う必要が あることの重要な部分のほとんどを行うことができます。
作者: Mark Jason Dominus, Plover Systems (mjd-perl-ref+@plover.com
)
この記事は最初は The Perl Journal ( http://www.tpj.com/ ) volume 3, #2 に登場しました。 許可を得て転載しています。
元のタイトルは Understand References Today でした。
Copyright 1998 The Perl Journal.
This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.
Irrespective of its distribution, all code examples in these files are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.