如何在Perl和Moose中创建不可变对象的循环图?

zou*_*oul 7 perl moose immutability

这看起来似乎是一个明显无望的案例,但是有一个技巧可以在Perl中创建不可变对象的循环图吗?像这样的东西:

package Node;
use Moose;
has [qw/parent child/] => (is => 'ro', isa => 'Node');

package main;
my $a = Node->new;
my $b = Node->new(parent => $a);
Run Code Online (Sandbox Code Playgroud)

现在,如果我想$a->child指出$b,我该怎么办?

Gre*_*con 5

您可以使用延迟初始化来玩游戏:

package Node;
use Moose;

has parent => (
  is => 'ro',
  isa => 'Node',
  lazy => 1,
  init_arg => undef,
  builder => '_build_parent',
);

has _parent => (
  is => 'ro',
  init_arg => 'parent',
);

has child => (
  is => 'ro',
  isa => 'Node',
  lazy => 1,
  init_arg => undef,
  builder => '_build_child',
);

has _child => (
  is => 'ro',
  init_arg => 'child',
  predicate => undef,
);

has name => is => 'ro', isa => 'Str';
Run Code Online (Sandbox Code Playgroud)

动态生成构建器和谓词:

BEGIN {
  for (qw/ parent child /) {
    no strict 'refs';

    my $squirreled = "_" . $_;

    *{"_build" . $squirreled} = sub {
      my($self) = @_;
      my $proto = $self->$squirreled;
      ref $proto eq "REF" ? $$proto : $proto;
    };

    *{"has" . $squirreled} = sub {
      my($self) = @_;
      defined $self->$squirreled;
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

这允许

my $a = Node->new(parent => \my $b, name => "A");
   $b = Node->new(child  =>     $a, name => "B");

for ($a, $b) {
  print $_->name, ":\n";
  if ($_->has_parent) {
    print "  - parent: ", $_->parent->name, "\n";
  }
  elsif ($_->has_child) {
    print "  - child: ", $_->child->name, "\n";
  }
}
Run Code Online (Sandbox Code Playgroud)

它的输出是

A:
  - parent: B
B:
  - child: A
Run Code Online (Sandbox Code Playgroud)

该代码可以用更优雅η转换,但驼鹿不会通过参数生成器方法.


per*_*rin 4

我必须去看看真正的不可变语言是如何做这样的事情的,我认为以下可能是一个合理的尝试。

use 5.10.0;
{

    package Node;
    use Moose;
    has [qw(parent child)] => ( isa => 'Node', is => 'ro' );

    sub BUILD {
        my ( $self, $p ) = @_;
        return unless exists $p->{_child};
        my $child = Node->new( parent => $self, %{ delete $p->{_child} }, );
        $self->meta->get_attribute('child')->set_value( $self, $child );
    }
}

say Node->new( _child => {} )->dump
Run Code Online (Sandbox Code Playgroud)

基本上,您不必尝试单独构建对象,而是让父级根据传入的参数自动激活子级。其输出是,我相信这是您想要的结构。

$VAR1 = bless( {
                 'child' => bless( {
                                     'parent' => $VAR1
                                   }, 'Node' )
               }, 'Node' );
Run Code Online (Sandbox Code Playgroud)