autovivificationとうまく付き合う

# autovivificationとうまく付き合う

こんにちは。 @debug-ito です。PlackとAnyEventが好きですがautovivificationも好きです。

今回は[autovivificationプラグマ](https://metacpan.org/pod/autovivification)について書いてみます。


## 検証環境

- Xubuntu 12.04 32bit
- perl v5.14.2 (Ubuntuレポジトリに入ってるやつ) 
- autovivification 0.12


## autovivificationって？

`undef`の入った変数が配列やハッシュとしてdereferenceされる際に配列やハッシュオブジェクトが自動的に生成される機能です。

公式ドキュメントには[perlref](http://perldoc.perl.org/perlref.html)に記載があります。

例えば下のような場合、

```perl
## 以下、全ての例でこの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してデータをブッこんでもよしなに構造を作ってくれます。

他にも、僕がよくやる例としては、

```perl
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が発生することもあるので注意が必要なのです。

例えばこんな場合。

```perl
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](https://metacpan.org/pod/Data::Transformer)とか使うと予想外の事態になりそうですね。

こういったautovivificationをクソマジメに防ぐにはこうします。

```perl
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](https://metacpan.org/pod/autovivification)はautovivificationの有効・無効を切り替えてくれるプラグマモジュールです。通常は、

```perl
no autovivification;
```

のように`no`キーワードとともに使い、autovivificationを部分的に無効化します。

これを使うと、先程の例はこう書けます。

```perl
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が発生します。

```perl
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するけど？

下の例を見て下さい。

```perl
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では実引数は参照渡しであり、呼び出された関数はその内容を書き換えることができます。

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