Posted at
PerlDay 12

autovivificationとうまく付き合う

More than 5 years have passed since last update.


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 さんです!