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

7
这似乎是一个明显无望的情况,但是否有技巧可以在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);

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

1
也许这是一个愚蠢的问题,但为什么不变性很重要呢? - Ether
1
对我来说,这使得推理“Node”对象更容易。其他原因请参见“不可变性”标签。 - zoul
3个回答

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';

动态生成构建器和谓词:

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;
    };
  }
}

这允许

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";
  }
}

它的输出是
A:
  - parent: B
B:
  - child: A

代码可以使用η-conversion更加优雅,但Moose不会将参数传递给构建器方法。

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

基本上,不是尝试单独构建对象,而是让父级根据传入的参数自动生成子级。这是输出结果,我认为这就是您想要的结构。
$VAR1 = bless( {
                 'child' => bless( {
                                     'parent' => $VAR1
                                   }, 'Node' )
               }, 'Node' );

1
不变和循环是矛盾的。这是不可变语言处理它的方式。例如,在Erlang中,您无法创建任何循环数据结构。涉及不变性是防止循环的方法,也是不变性涉及的原因。 - Hynek -Pichi- Vychodil

1

我对Moose还很陌生,但触发器可以起作用吗?

use Modern::Perl;

package Node;
use Moose;
has 'parent' => (
    is => 'ro',
    isa => 'Node',
    trigger => sub{
        my ($self, $parent) = @_;
        $parent->{child} = $self unless defined $parent->child;
    }
);

has 'child' => (
    is => 'ro',
    isa => 'Node',
    trigger => sub{
        my ($self, $child) = @_;
        $child->{parent} = $self unless defined $child->parent;
    }
);

package main;
my $p = Node->new;
my $c = Node->new(parent => $p);

say $p, ' == ', $c->parent;
say $c, ' == ', $p->child;

把Moose对象当作HashRef处理是不公平的。今天它可能是一个,但明天并不能保证它还是。 - Piers Cawley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接