views:

106

answers:

2

I've got a Moose object:

class My::Game {
  has 'players' => (isa => 'Set::Object', ...)
  has 'action_sequence' => (isa => 'ArrayRef[My::Game::Action]', ...)
}

Now I want to be able to clone this object with a call like $game2 = $game->clone; How do I deep clone it so that the objects in the ArrayRef are cloned? And more trickily, the Set::Object?

I've looked at MooseX::Clone, but I'm unclear how to apply it to this case. Example code would be appreciated.

Thanks!

+2  A: 

I haven't used any of the pieces here (MooseX::Clone, MooseX::Compile, and Set::Object), so here's just a rough outline of where I'd start, from my review of the docs and general knowledge of Moose architecture:

  • MooseX::Clone gives your object a clone() method, which will then call clone() on each of your attributes with the 'Clone' trait.
  • so, you need to add a clone() method to both of your attributes...
  • it would be my ambitious hope that MooseX::Clone can handle native attribute arrayrefs, but since it probably doesn't, you may get to extend that feature yourself (i.e. if 'action_sequence' has the option traits => ['Array'], it is smart enough that the clone method is actually defined via handles => { clone => [ 'map', 'clone' ] } -- i.e. clone the attribute by calling clone() on each of the member elements) -- here is likely where you would be submitting your first patch
  • Set::Object's documentation suggests that you can clone objects reliably with Storable::dclone (which you should verify, and flog the author and/or supply patches if this is not correct)
  • and now you need to add a clone() method to the Set::Object attribute, which the docs suggest you do with MooseX::Compile (and here is where you should come on irc.perl.org #moose and give us a blow-by-blow of your progress, so we can make a documentary about it later) :)
Ether
I think you just need to use the `StorableClone` trait on the Set::Object attribute; that should automatically use `dclone` on it. (StorableClone is part of MooseX::Clone.)
cjm
A: 

Turns out that simply adding the MooseX::Clone role to the class provides a clone() method that recursively clones attributes.

  • For hashref/arrayref attributes, it copies structures.
  • For scalars (including references) it simply does a shallow copy of the reference.
  • If you add traits => ['Clone'] to the attribute, it will recursively clone the attribute by calling clone() on the attribute value.

To support cloning Set::Object, I ended up creating a trait called CloneByCoercion by subclassing the Clone trait, parameterized with the type to coerce to/from before cloning.

So to use it, I wrote:

has 'blah' => (isa => 'Set::Object', is => rw,
  traits => ['CloneByCoercion' => {to=>'ArrayRef'}]
);

MooseX::Types::Set::Object provides coercions to and from ArrayRef (although I needed to patch a bug in it: the coercion to ArrayRef should return a reference, not a list)

I also modified MooseX::Clone to keep an objects-seen hash, so that it supports cloning interlinked object structures with circular references.

I'll eventually get around to putting this stuff up on CPAN or submitting patches to the modules.

David