Thanks Stevan. As it happens I'm already using the My::Framework approach - the bit that I was getting stuck on (which now seems fairly obvious) was that I was "use"ng packages in the My::Types library (required for some of the coercions).
e.g. to support the "My::Versioned" role being initialised from a string I had something like: * package My::Object; * use Moose; with 'My::Versioned'; * package My::Versioned; * use My::Types qw( Version ); use Moose::Role; has 'version' => (is => 'rw', isa => 'Version'); * package My::Types * use MooseX::Types -declare ['Version']; > use My::Version; subtype 'Version' => as 'Object' => where { $_->isa( 'My::Version' ) } => message { "object '$_' does not appear to be a valid Version" }; coerce 'Version' => from 'Str' => via { My::Version::init_from_string( $_ ) }; I localised the "use" statements in the My::Types package to take them out of the compile-time loop: - use My::Version; coerce 'Version' => from 'Str' - => via { My::Version::init_from_string( $_ ) }; + => via { use My::Version; My::Version::init_from_string( $_ ) }; Is this a really horrible hack? 2009/1/30 Stevan Little <stevan.lit...@iinteractive.com> > Ian, > > If your looking to decrease fragility in a framework, but don't want to > have to enforce some kind of "you must load this 3 modules in this order" > rule, then you might want to look into the docs on extending and embedding > Moose. Then you do can just do: > > package My::App; > use My::Framework; # load Moose and all your deps in strict order > > - Stevan > > > > > On Jan 30, 2009, at 3:37 AM, Ian Sillitoe wrote: > > Stevan, >> >> Thanks to your comments I managed to fix my code - it was driving me nuts >> so I really appreciate your help. I now need to go back through my changes >> to find out exactly which bit fixed the problem so I understand it better. >> >> The process of untangling module dependencies in my local code base wasn't >> terribly pleasant even though I don't _think_ I'm doing anything too unusual >> or circuitous. So while I'm obviously happy that my tests now pass, I'm >> worried that things are left a little fragile (although I'm aware the >> perceived fragility is most likely caused by my lack of understanding, which >> I'm working on). >> >> One of my goals at $work was to write an internal framework that would be >> easy for others to use, understand and extend - I'm now concerned that I'm >> leaving a fairly large and unpleasant gotcha lurking under the surface. >> Happy for hear any more ideas about techniques to help avoid this in the >> future. >> >> Cheers, >> >> Ian >> >> >> >> >> >> 2009/1/29 Stevan Little <stevan.lit...@iinteractive.com> >> I tend to do the following: >> >> - make a MyApp::Types package that handles *all* custom type definitions >> and type coercions >> - make all other packages load MyApp::Types (even if they dont need it, >> because their deps might need it) >> >> Then as far as circular refs are concerned, they are tricky and each case >> is a little different. The key is to map out what you need and the force >> things to load in that order (this is where the global MyApp::Types can be >> really handy). >> >> Also, find_type_constraint is your friend, it is exported from >> Moose::Util::TypeConstraints and can basically be used to detect if the type >> already exists or not. Something like: >> >> if (find_type_constraint('Foo')) { >> # do something wiht the Foo type here, like add a coercion >> } >> >> Of course the ideal situation is that you break your circular refs >> instead, but sometimes that is just not possible. >> >> - Stevan >> >> >> On Jan 29, 2009, at 11:59 AM, i...@sillit.com wrote: >> >> Stevan, >> >> Many thanks for getting back so quickly - that sounds exactly the sort >> of thing that must be happening. >> >> Before I dive in - could I ask what the recommended best practice for >> avoiding circular "use" cases is (if any)? I've generally tried to >> "use" all the modules required by the package to keep each package as >> independent as possible without loading everything unneccessarily. Or >> am I missing something? >> >> Cheers >> >> Ian >> >> On 29/01/2009, Stevan Little <stevan.lit...@iinteractive.com> wrote: >> Ian, >> >> Types can sometimes be very sensitive to load order since they are >> really done at runtime (it is the runtime immediately following >> compile time, but still runtime). Based on your description I think >> you are getting bitten by the auto-creation of types (if Moose doesn't >> know the type it makes it into a Object subtype that isa(<name of >> type>)). >> >> I would check to make sure that you do not have any circular >> dependencies in how you load your modules, Perl will handle these >> fine, but Moose types will be sensitive to the order in which the >> circle is traversed. >> >> - Stevan >> >> On Jan 29, 2009, at 6:54 AM, Ian Sillitoe wrote: >> >> Hello, >> >> I have a problem with subtype/coercions (MooseX::Types library) that >> I've >> spent ages trying to understand, then ages trying abstract into a >> simple >> test case but all simple cases seem to work as expected but just not >> in my >> larger code base. I was hoping that if I put the symptoms up someone >> may >> spot what is going on and put me out of my misery (or at least point >> me to >> where I should continue my digging). >> >> Please excuse any typos in the (incomplete) example code below. I >> have coded >> up a full version of this simple scenario and I can't replicate the >> error >> I'm getting in my real code base anyway - so this is meant purely for >> illustration. >> >> # Types library >> package My::Types; >> use Moose; >> use MooseX::Types -declare [qw( Version DomainID )]; >> use My::Version; >> >> subtype 'Version', >> as 'Object', >> where { $_->isa( 'My::Version' ) }; >> >> coerce 'Version', >> from 'Str', >> via { My::Version::init_from_string( $_ ) }; >> >> subtype 'ObjectID', >> as 'Str'; >> >> # Role to associate versions with objects >> package My::Role::Verisoned; >> use Moose::Role; >> use My::Types qw(Version); >> has 'version' => (is => 'rw', isa => 'Version', coerce => 1); >> >> # Versioned Object1 >> package My::Object1; >> use Moose; >> with 'My::Role::Versioned'; >> >> # Versioned Object2 >> package My::Object2; >> use Moose; >> use My::Types qw( DomainID ); >> use My::Object1; >> with 'My::Role::Versioned'; >> >> has 'domain_id' => ( is => 'rw', isa => 'DomainID' ); >> >> sub get_object1 { >> my $self = shift; >> return My::Object1->new( version => $self->version ); >> } >> >> In this simple case, the subtype/coerce works as expected in both >> objects >> (the type constaint of the 'version' attribute is the identical) >> >> $o2 = My::Object2->new( version => '3.2' ); >> $o1 = $o2->get_object1; >> >> warn "Object1: " . >> My::Object1->meta->get_attribute_map->{version}->{type_constraint}- >> dump; >> warn "Object2: " . >> My::Object2->meta->get_attribute_map->{version}->{type_constraint}- >> dump; >> >> Object1: $VAR1 = bless( { >> 'compiled_type_constraint' => 'CODE(0xa41e310)', >> 'parent' => 'My::Version', >> 'coercion' => >> 'Moose::Meta::TypeCoercion=HASH(0xa991c70)', >> 'name' => 'Version', >> 'constraint' => 'CODE(0x9eb4600)', >> 'package_defined_in' => 'My::Types' >> }, 'Moose::Meta::TypeConstraint' ); >> >> Object2: $VAR1 = bless( { >> 'compiled_type_constraint' => 'CODE(0xa41e310)', >> 'parent' => 'My::Version', >> 'coercion' => >> 'Moose::Meta::TypeCoercion=HASH(0xa991c70)', >> 'name' => 'Version', >> 'constraint' => 'CODE(0x9eb4600)', >> 'package_defined_in' => 'My::Types' >> }, 'Moose::Meta::TypeConstraint' ); >> >> However, in what seems to be a similar scenario in my live code, I >> get the >> error that the 'version' attribute doesn't pass the type constraint >> when >> creating an instance of Object1 inside Object2::get_object1 (it no >> longer >> seems to be aware of the subtype/coercion in My::Types): >> >> Attribute (version) does not pass the type constraint because: >> Validation >> failed for 'Version' failed with value >> My::Version::V3_2_0=HASH(0x1aea0930) >> at .../Moose/Meta/Attribute.pm line 415 >> >> When I look at the type constraint of the 'version' attribute in >> these two >> classes they now appear different: >> >> warn "Object1: " . >> My::Object1->meta->get_attribute_map->{version}->{type_constraint}- >> dump; >> warn "Object2: " . >> My::Object2->meta->get_attribute_map->{version}->{type_constraint}- >> dump; >> >> Object1: $VAR1 = bless( { >> 'compiled_type_constraint' => 'CODE(0x1ae87c00)', >> 'parent' => 'Object', >> 'hand_optimized_type_constraint' => >> $VAR1->{'compiled_type_constraint'}, >> 'name' => 'Version', >> 'constraint' => 'CODE(0x1a3c4b80)', >> 'class' => 'Version' >> }, 'Moose::Meta::TypeConstraint::Class' ); >> >> Object2: $VAR1 = bless( { >> 'compiled_type_constraint' => 'CODE(0x1a929c10)', >> 'parent' => 'My::Version', >> 'coercion' => >> 'Moose::Meta::TypeCoercion=HASH(0x1ae9da00)', >> 'name' => 'Version', >> 'constraint' => 'CODE(0x1a3c4b80)', >> 'package_defined_in' => 'My::Types' >> }, 'Moose::Meta::TypeConstraint' ); >> >> >> I hope that difference shows what I might be doing wrong or where to >> start >> looking (parent => 'Object'? hand_optimized_type_constraint?). This >> change >> appears to be related to which modules have been "use"d -- in my >> live code I >> can remove this error by *taking out* the equivalent of "use >> My::Types" on >> Object2. >> >> Very much appreciate any help/pointers. >> >> Cheers, >> >> Ian >> >> >> -- >> Ian Sillitoe >> CATH Team -- http://cathdb.info >> >> >> >> >> -- >> Ian Sillitoe >> CATH Team -- http://cathdb.info >> >> >> >> >> -- >> Ian Sillitoe >> CATH Team -- http://cathdb.info >> >> > -- Ian Sillitoe CATH Team -- http://cathdb.info