2010年9月27日
モダンなPerlを「読む」上で覚えておくとよい構文 第2回「リストを理解すれば配列とハッシュをより活用できる」
第1回から大分時間が空きましたが、なんと続きます。「次回は無名関数について書く」とか書いていましたが、先にリストについて言及することにします。
混同されがちですがこのエントリーでは「リスト」と「配列」を厳密に違うものとして扱います。結論を先に簡単に言ってしまうと、リストを配列に代入すれば配列になるし、リストをハッシュに代入すればハッシュになるということです。
Perlの式は値を返す
サブルーチンに限らずPerlのあらゆる式は評価された値を返します。返された値は代入先があれば代入され、代入先がなければ捨てられます。
値を返さないケース
ブロックは値を返しません(doを使えばブロックに値を返させることが出来ます)。例外的にuse文やpackage文は値を返しません。この二つはコンパイル時にコードを実行する前に最初に評価されるので値の返しようがありません。
さて、本題です。Perlの式の値の返し方には2種類あります。スカラーとリストです。スカラーを返すかリストを返すかはコンテキストに応じて変化することもあります。
リストコンテキストで返されるものがリスト(当たり前!)です。また、以下のようにすればリストを直に記述できます。
(1, 2, 3, 'hoge');
qw/1 2 3 hoge/;
「なんだ、配列じゃないか。」
いいえ、違います。そう思う人は以下のコードを実行してみて下さい。驚きの結果が得られることでしょう。
use Data::Dumper;
my $hoge = \(1, 2, 3, 'hoge'); #配列をリファレンス化している...?
print Dumper $hoge;
リストを配列リファレンスに代入したい場合は、リストをブラケットで囲めば配列リファレンスを返します。
my $hoge = [1, 2, 3, 'hoge'];
リストはハッシュキーにもなれる(けど使う機会はまずない)
以下のようなことも可能です。
my %hash;
$hash{qw/1 2 3/} = 'hoge';
print $hash{qw/1 2 3/}; # hoge
これはどういう事かというと、以下のコードを実行すれば分かります。
my %hash;
$hash{qw/1 2 3/} = 'hoge';
my ($key) = keys %hash; #キーを取り出す。
$key =~ s/(.)/'\x'.unpack('H2', $1)/eg; #バイトごとに16進にエンコーディング
print $key; # \x31\x1c\x32\x1c\x33
値が'\x1c'という文字で区切られていることが分かります。これは印字されない制御文字(FS)です。つまり印字されない文字を区切り文字に使った文字列(バイト列)がキーになっているという単純な仕掛けです。
なのでバイナリデータをハッシュキーにしたりすると上手くいかない可能性があります。
また、この区切り文字には特殊変数$;
を使ってアクセスすることができます。
ややマニアックだけどかなりどうでも良いネタでした。
冒頭に書いたとおり、リストを配列に渡せば配列になり、ハッシュに渡せばハッシュになります。
my @arr = (1, 2, 3, 'hoge');
print $arr[1]; # 2
my %hash = (1, 2, 3, 'hoge'); # (1 => 2, 3 => 'hoge') と同じ
print $hash{3}; # hoge
配列はリストコンテキストで評価された場合リストを返します。ハッシュも同様にリストを返します。なので以下のようなことも可能です。
my @arr = (1, 2, 3, 'hoge');
my %hash = @arr;
print $hash{3}; # hoge
@arr = %hash; # こんなことも可能。ハッシュの順番が保証されないので、普通はやらない
これを利用して、名前付き引数的な関数を実現可能です。
#カッコの中はリスト。ファットコンマは単に可読性のため。
func(
hoge => 'fuga',
piyo => 11,
);
sub func {
#配列をハッシュに代入していて一見意味不明だが、配列がリストを
#返し、それがハッシュにわたっていると考える。
%hash = @_;
if ($hash{hoge} ){ ... }
}
また、以下のようにmapを組み合わせて、配列の各要素をキーとして、ハッシュを作成することが可能です。
my @arr = qw/hoge fuga piyo/;
%hash = map { $_ => 1 } @arr; #各要素を1で初期化
if($hash{hoge}){# @arr内に'hoge'が存在するかをチェックできる
...
}
mapやgrepは難解に思えますが、リスト操作には欠かせません。トリッキーに思えることも多いかと思いますが、そのうち慣れてくるでしょう。ただ、やりすぎはいけません。
リファレンス
[]や{}を使って、配列/ハッシュリファレンスを作成することが出来ますが、内部に単なるリストではなく、リストを返す式を書くこともできます。以下のような感じです。
$arr_ref = [1,2,3]; # 普通の書き方
$arr_ref = [qw/hoge fuga piyo/]; # こんな書き方も
$arr_ref = [@arr]; # \@arr と実質同じだけど、こう書かれることも多い
$arr_ref = [split //, $str]; #文字列を一文字づつ切り出した結果を配列リファレンスに
最後の式は少しびっくりするかもしれません。人によって多寡はありますが、これくらいだったら良く使われているように思います。mapやgrepがこの中で使われることもあります。例に因って自分で書くときはやりすぎは厳禁です。
また、オブジェクトのコンストラクタに引数を渡すときに、名前付き引数的な渡し方とハッシュリファレンスを渡すやりかたが両方可能になっている場合も結構あったりします。以下のような感じです。
my $obj1 = Hoge->new( #名前付き引数的な渡し方
hoge => 'fuga',
);
my $obj2 = Hoge->new({ #ハッシュリファレンスを渡す渡し方
hoge => 'piyo',
});
package Hoge;
sub new{
my $cls = shift;
#渡されたのが、リスト(名前付き引数)か、ハッシュリファレンスか判別
my $self =
ref $_[0] eq 'HASH' ? shift : {@_};
bless $self, cls;
}
{@_}
という書き方が最初慣れないかも知れませんが「配列がリストとして展開されて、それがハッシュリファレンスになる」と考えれば理解可能でしょう。
まとめ
「何でここでは配列なのに、こっちではハッシュなんだ?」とか思ってしまうこともあるかもしれません。
全てリストを介してつながっている
と考えれば話は簡単です。この辺りのリストの扱いを理解してくると「PerlがLISPの影響を受けている」と言われる所以も分かってくるでしょう。