autovivificationとうまく付き合う
こんにちは。 @debug-ito です。PlackとAnyEventが好きですがautovivificationも好きです。
今回はautovivificationプラグマについて書いてみます。
検証環境
- Xubuntu 12.04 32bit
- perl v5.14.2 (Ubuntuレポジトリに入ってるやつ)
- autovivification 0.12
autovivificationって?
undef
の入った変数が配列やハッシュとしてdereferenceされる際に配列やハッシュオブジェクトが自動的に生成される機能です。
公式ドキュメントにはperlrefに記載があります。
例えば下のような場合、
## 以下、全ての例でこの3行は有効とします
use strict;
use warnings;
use Data::Dumper;
my $data;
$data->{foo}{bar}[2] = "hoge";
print Dumper $data;
## $VAR1 = {
## 'foo' => {
## 'bar' => [
## undef,
## undef,
## 'hoge'
## ]
## }
## };
とまあ、初期化もしてない変数$data
をいきなりdereferenceしてデータをブッこんでもよしなに構造を作ってくれます。
他にも、僕がよくやる例としては、
my @fruit_list = qw(apple melon orange peach pineapple);
my $result;
foreach my $fruit (@fruit_list) {
my $len = length($fruit);
push(@{$result->{$len}}, $fruit);
}
print Dumper $result;
## $VAR1 = {
## '6' => [
## 'orange'
## ],
## '9' => [
## 'pineapple'
## ],
## '5' => [
## 'apple',
## 'melon',
## 'peach'
## ]
## };
と、undef
なフィールドに対していきなりpush
できたりします。便利ですね!
autovivificationが牙を剥く瞬間
こんな便利なautovivificationですが、時としてビックリさせられることがあります。
上の例のように書き込み操作ではそうでもないのですが、読み込み操作でautovivificationが発生することもあるので注意が必要なのです。
例えばこんな場合。
my %data;
if(defined($data{foo}{bar}{buzz}{hoge})) {
print "foo bar buzz hoge: $data{foo}{bar}{buzz}{hoge}\n";
}
print Dumper \%data;
## $VAR1 = {
## 'foo' => {
## 'bar' => {
## 'buzz' => {}
## }
## }
## };
おや、%data
には一切代入をしていないのにいろいろautovivifyされましたね。
これは、defined($data{foo}{bar}{buzz}{hoge})
を評価する際に$data{foo}
や$data{foo}{bar}
などが順番にハッシュとしてdereferenceされることでそれらがautovivifyされているからだと考えられます。defined
ではなく、exists
を使っても同様です。
まあ、だからといって大抵の場合どうってことないんですが、この後でData::Transformerとか使うと予想外の事態になりそうですね。
こういったautovivificationをクソマジメに防ぐにはこうします。
my %data;
if(defined($data{foo}) && defined($data{foo}{bar})
&& defined($data{foo}{bar}{buzz}) && defined($data{foo}{bar}{buzz}{hoge})) {
print "foo bar buzz hoge: $data{foo}{bar}{buzz}{hoge}\n";
}
print Dumper \%data;
## $VAR1 = {};
はい、だいぶダルくなってきました。
"no autovivification"
autovivificationはautovivificationの有効・無効を切り替えてくれるプラグマモジュールです。通常は、
no autovivification;
のようにno
キーワードとともに使い、autovivificationを部分的に無効化します。
これを使うと、先程の例はこう書けます。
no autovivification;
my %data;
if(defined($data{foo}{bar}{buzz}{hoge})) {
print "foo bar buzz hoge: $data{foo}{bar}{buzz}{hoge}\n";
}
print Dumper \%data;
## $VAR1 = {};
no autovivification
と先頭に書くだけでOKです。楽チンですね!
no autovivification
が有効になっている状態でも、代入を行うとautovivificationが発生します。
no autovivification;
my $data;
if(!defined($data->{foo}{bar})) {
print Dumper $data;
print "----------\n";
$data->{foo}{bar} = "hoge";
print Dumper $data;
}
## $VAR1 = undef;
## ----------
## $VAR1 = {
## 'foo' => {
## 'bar' => 'hoge'
## }
## };
まあ代入するなら普通はautovivifyしてほしいですよね。サイコーですね!!
なお、設定次第で代入時のautovivificationも禁止することができます。詳しくはドキュメントを参照して下さい。
あれ、"no autovivification"って書いたのにautovivifyするけど?
下の例を見て下さい。
no autovivification;
sub print_if_any {
my ($str) = @_;
if(defined $str) {
print "$str\n";
}
}
my $data;
if(defined $data->[0]) {
print "$data->[0]\n";
}
print Dumper $data;
print "-----------\n";
print_if_any($data->[0]);
print Dumper $data;
## $VAR1 = undef;
## -----------
## $VAR1 = [];
「$data->[0]
が存在したらそれをprintする」という処理を2通りの書き方で書いているだけですが、関数print_if_any
を経由するとautovivificationが起こってしまっています。
これは、関数への実引数渡しが代入と同等に扱われているためだと考えられます。Perlでは実引数は参照渡しであり、呼び出された関数はその内容を書き換えることができます。
no autovivification;
sub add_elem {
push(@{$_[0]}, "hoge");
}
my $data;
add_elem($data);
print Dumper $data;
## $VAR1 = [
## 'hoge'
## ];
このようなこともあるため、関数への実引数渡しではautovivificationが発生してしまいます。同様のことはfor
文でも起こるようです。
autovivificationプラグマのドキュメントをよく読めば書いてあることなんですが、わりと盲点になりがちなトコロだと思っています。
まとめ
Perlの便利な機能"autovivification"と、それを部分的に無効化するプラグマモジュールautovivificationについてご紹介しました。"no autovivification"と書くだけでいい感じの挙動になるのは嬉しいですね。
さて、次回は中国から参戦の @chenryn さんです!