weakenは何に対して効くか

PerlのScalar::Util にある weaken について学んでいたところ、誤解していた点があったので覚え書き。

以下のように、同じリファレンス値を持つ変数を2つ作り、片方だけを weaken することを考えます。

use strict;
use warnings;
use feature qw(say);
use Scalar::Util qw(weaken isweak);

my ($a, $b);
$a = $b = {};
weaken $a;

say "\$a = $a is ", isweak $a ? 'weak' : 'strong';
say "\$b = $b is ", isweak $b ? 'weak' : 'strong';

自分は、$aも$bもリファレンスとしては同じ値なのだから、$aをweakenすれば$bも弱くなる、という風に思っていたのですが、結果は以下の通りでした。

$ perl a.pl
$a = HASH(0xc43ad8) is weak
$b = HASH(0xc43ad8) is strong

リファレンスの値としては同じにも関わらず、$aのみが弱くなっています。

これはつまりどういうことかと考えたところ、weakenはHASH(0xc43ad8)というようなリファレンスとしての値に対して働くのではなく、リファレンスを格納する変数名(のようなもの)に対して働くものなのだ、と解釈すると納得が行きました。

※「変数名(のようなもの)」の例:

引数の値ではなく変数名に対して働くということで言うと、weaken は my や local に似ているように思いました。

* * *

もう少し実際の利用例に近い形で言うと、例えば以下のように、互いに参照し合うオブジェクトがあり、一方の参照を弱くすることでブロック終端でオブジェクトの変数が開放されることを示す例を考えます。(コードは404 Blog Not Found:perl - Data::Decycle で悪循環を断とう!の記事にあったものを元にしてます)

use strict;
use warnings;
use feature qw(say);
use Scalar::Util qw(weaken);

{
    package Foo;
    sub new { bless {}, $_[0] }
    sub DESTROY { warn "destroy $_[0]" }
}

{
    package main;
    my $parent = Foo->new;
    my $child = Foo->new;
    $parent->{child} = $child;
    $child->{parent} = $parent;
    weaken $child->{parent};
}

warn "end";

__END__
$ perl a.pl
destroy Foo=HASH(0xc43c70) at a.pl line 9.
destroy Foo=HASH(0xc9b510) at a.pl line 9.
end at a.pl line 21.

で、このweakenの行において、$child->{parent} = $parent だからと言って weaken $parent としては意味がない、ということです。

* * *

なお、この辺りのことはScalar::UtilのPODにも以下のように説明されていました。

Note that if you take a copy of a scalar with a weakened reference, the copy will be a strong reference.

    my $var;
    my $foo = \$var;
    weaken($foo);                       # Make $foo a weak reference
    my $bar = $foo;                     # $bar is now a strong reference