InsideOutテクニック

Perl Best Practices(PBP)でも紹介されていた,Inside-Outテクニックというものがあります。オブジェクトの完全なカプセル化が目的なわけですよ。以下,基本的な実装を示します。

# Foo.pm
package Foo;
use strict;
use Scalar::Util qw(refaddr);
my %bar_of; # field "bar"
sub new{
  my $self = bless \do{ my $anon }, shift;
  $bar_of{refaddr $self} = shift;
  return $self;
}
sub DESTROY{
  my $self = shift;
  delete $bar_of{refaddr $self};
  return;
}
sub get_bar{
  my $self = shift;
  return $bar_of{refaddr $self};
}
__END__
#!perl -w
use strict;
use feature 'say';
use Foo;
my $x = Foo->new(42);
say $x->get_bar(); # => 42
__END__

確かに,これで生のプロパティに外部からアクセスすることはできません。気になるのは速度です。他のカプセル化を行うソリューションと比べると最も高速である的なことがPBPに書かれていましたが,実際に計ってみると普通のハッシュリファレンスによる実装の方が明らかに速いです。というのも考えてみれば当然で,refaddr $selfがあるからその分遅いに決まってます。Class::MOPやMooseのデフォルト*1がInsideOutオブジェクトにならないのは,おそらく速度の問題もあるのでしょう。
しかしですよ。考えてみれば1つのオブジェクトにつきスカラースロットが一つ余っているわけで,そこにrefaddr $selfを入れてしまえば速度の問題は解決するのでは?

# ...
sub new{
  my $self = bless \do{ my $id }, shift;
  $$self = refaddr $self
  #...
}
# ...
sub get_bar{
  my $self = shift;
  return $bar{$$self};
}
# ...

これならプロパティアクセスのコストは一度のスカラーでリファレンスとハッシュ検索ですから,ハッシュのデリファレンスと大差ないパフォーマンスになるはず。
もっとも,欠点もあります。$x = Foo->new($y)とした後,$$x = 10などとすることでオブジェクトを壊すことができたり,他のFooインスタンスのrefaddr($z)を代入すればデータ構造がおかしなことになる可能性もあるでしょう。しかし,任意のオブジェクトのプロパティに直接触ることはできません*2。うっかりを防ぎたいならnew()のときにInternals::SvREADONLY($$self, 1)などをしてロックしてしまえばいいですし。
というわけで,工夫すれば速度面では問題なさそうです。あとはそれをうまくモジュール化できればいいんですがInsideOutを実装するモジュールは沢山ありすぎてどれがいいのか分かりませんね。あとで時間があれば比較します。

*1:プラグインとしてはMooseX::InsideOutというものもあるようです。

*2:といっても,BやPad::Walkerなどを使えば,それこそ「なんでも」できますが