Perlの循環参照とその解決について

# Perlにおける循環参照

Perlにおける｢循環参照｣は, あるリファレンスからリファレンスをたどっていった際, 最初のリファレンスに戻ることができる状態のことを言います.

...といってもわからないので, 実例を見てみましょう:

```perl
use strict;
use warnings;

my $hash_ref  = { a => 1, b => 2 };
my $array_ref = [ $hash_ref, 1, 2, 3 ];
$hash_ref->{array} = $array_ref;

print "$hash_ref\n";
print "$hash_ref->{array}->[0]\n";
```

このコードを実行すると, 次のようになります.

```
HASH(0x7f8f399b3670)
HASH(0x7f8f399b3670)
```

これは, `$hash_ref`と`$hash_ref->{array}->[0]`が同じものを指しているということであり, つまりは`$hash_ref`から`$hash_ref->{array}->[0]`とリファレンスをたどっていくことで, 最初の`$hash_ref`が参照できる, ということを意味します.

循環参照や, それによって生じるメモリリークについては, 2011年の資料ではありますが @hiratara さんの[循環参照のはなし](http://www.slideshare.net/hiratara/ss-10539893)というスライドも参考になりますので, 見てみると良いと思います.

# 循環参照の発見

こういった循環参照の発見には, [Devel::Cycle](https://metacpan.org/pod/Devel::Cycle)モジュールを使うのが便利です.

先ほどのコードの場合,

```perl
use strict;
use warnings;
use Devel::Cycle;

my $hash_ref  = { a => 1, b => 2 };
my $array_ref = [ $hash_ref, 1, 2, 3 ];
$hash_ref->{array} = $array_ref;

find_cycle($hash_ref);
```

このコードを実行すると, 次のような出力が得られます.

```
Cycle (1):
                         $A->{'array'} => \@B
                               $B->[0] => \%A
```

`$hash_ref`には, 1つの循環参照が存在する, というわけです.

# 循環参照の解決

この循環参照を解決する方法の1つが, ｢リファレンスを弱くする｣ことです.
詳しい説明は省きますが, 循環参照のうち, 1つのリファレンスを｢弱く｣することでメモリリークを防ぐことができます.

リファレンスを弱くするためには, 次のように[Scalar::Util](https://metacpan.org/pod/Scalar::Util)の[`weaken`](https://metacpan.org/pod/Scalar::Util#weaken)を利用します.

```perl
use strict;
use warnings;
use Devel::Cycle;
use Scalar::Util;

my $hash_ref  = { a => 1, b => 2 };
my $array_ref = [ $hash_ref, 1, 2, 3 ];
$hash_ref->{array} = $array_ref;

print "before:\n";
find_cycle($hash_ref);

Scalar::Util::weaken($hash_ref->{array});

print "after:\n";
find_cycle($hash_ref);
```

実行結果は次の通りです.

```
before:
Cycle (1):
                         $A->{'array'} => \@B
                               $B->[0] => \%A

after:
```

`Scalar::Util::weaken($hash_ref->{array});`で, `$hash_ref->{array}`が示す, 配列へのリファレンスが弱められています.
そのため, リファレンスを弱くする前(`before:`の部分)は循環参照が生じていますが, その後(`after:`の部分)は何も表示されておらず, 循環参照が解決されていることがわかります.

# オブジェクトと循環参照

こういった循環参照が起きやすい場面の1つが, ｢子のオブジェクトが親のオブジェクトを参照する｣というシチュエーションです(今回こういった記事を書いたのは, お恥ずかしながらまさにこのシチュエーションでハマってしまったからです...).

次のコードについて考えます:

```perl
package Parent;
use Mouse;

has child => (
    is      => 'ro',
    isa     => 'Child',
    default => sub { Child->new( parent => $_[0] ) },
);

package Child;
use Mouse;

has parent => (
    is       => 'ro',
    isa      => 'Parent',
);
```

Parentを`new`すると, 自動的にChildも`new`され, このChildはParentから`child`メソッドで参照することができるようになります.
更に, Childは`parent`メソッドで自分自身を生成したParentを参照することができます.

Parentのオブジェクトを生成して, Devel::Cycleで検証してみましょう.

```
use strict;
use warnings;
use Devel::Cycle;

my $parent = Parent->new;
find_cycle($parent);
```

次のように, ParentとChildで循環参照が発生していることがわかります.

```
Cycle (1):
                 $Parent::A->{'child'} => \%Child::B
                 $Child::B->{'parent'} => \%Parent::A
```

この循環参照も, 先ほどと同じようにScalar::Utilの`weaken`で解決できます.

```perl
use strict;
use warnings;
use Devel::Cycle;
use Scalar::Util;

my $parent = Parent->new;

print "before:\n";
find_cycle($parent);

Scalar::Util::weaken($parent->child);

print "after:\n";
find_cycle($parent);
```

実行結果は次の通りです.

```
before:
Cycle (1):
                 $Parent::A->{'child'} => \%Child::B
                 $Child::B->{'parent'} => \%Parent::A

after:
```

# 余談: Mouseの`weak_ref`

Mouseでは, 次のように`weak_ref`を指定することで, `weaken`を適用した時と同じ状態を作ることができます.

```
package Parent;
use Mouse;

has child => (
    is      => 'ro',
    isa     => 'Child',
    default => sub { Child->new( parent => $_[0] ) },
);

package Child;
use Mouse;

has parent => (
    is       => 'ro',
    isa      => 'Parent',
    weak_ref => 1, # 弱いリファレンスにする
);

package main;
use strict;
use warnings;
use Devel::Cycle;

my $parent = Parent->new;

print "result:\n";
find_cycle($parent);
```

実行結果は次の通りで, `weaken`を使うことなく, 弱いリファレンスによって循環参照が解決されていることがわかります.

```
result:
```

# さいごに

Perlにおける循環参照と, Scalar::Utilの`weaken`を利用した解決法について書きました.

リファレンスはPerlを学習する一つの｢山｣ですが, これが理解できれば複雑なデータ構造をPerlで表現出来るようになり, Perlで出来ることが一気に広がります.
とはいえ気をつけないと, このような｢循環参照｣によって問題が生じてしまうので, 頭の片隅に置いておくと良いのではないでしょうか.