Read only attributes being filled without writer method in Moose - perl

I'm using Moose to create an object oriented class in Perl.
I have a number of attributes which I want to be read only which I've declared like this:
package BioIO::SeqIO;
use Moose;
use namespace::autoclean;
use MooseX::StrictConstructor;
use MooseX::Types::Moose qw(ArrayRef HashRef Int Str);
use FinalTypes::MyTypes qw(FileType);
has '_gi' => (isa => ArrayRef,
is => 'ro',
init_arg => undef,
writer => '_writer_gi');
I also have a BUILDER method which looks like this:
sub BUILD {
# Confessing with usage string if incorrect number of arguments used.
#_ == 2 or confess getErrorString4WrongNumberArguments();
# Initializing local variable with subroutine input.
my ($self) = #_;
# Creating BioIO::SeqIO object for GenBank or FASTA file.
$self->fileType =~ /genbank/i ? $self->_getGenbankSeqs() : $self->_getFastaSeqs();
}
My code works fine, however, I get a warning with the following test:
dies_ok {BioIO::SeqIO->new(filename => $fileNameIn, fileType => 'fasta', => _gi => [])} '... dies when _gi sent to BioIO::SeqIO constructor';
Here is the warning:
Use of uninitialized value $gi in hash element at BioIO/SeqIO.pm line 256, <$fh> chunk 1.
Lastly, here is line 256 for that error:
$hashDef{$gi} = $def;
I think that I'm getting a warning because the program is not dying as soon as the user attempts to write to _gi, however, I don't know how to ensure this happens?

In the attribute definition, note that the init_arg is set to undef, i.e. deleted:
has '_gi' => (isa => ArrayRef,
is => 'ro',
init_arg => undef,
writer => '_writer_gi');
The init arg is the name of the argument in the constructor call. As it is set to undef, you cannot initialize it via the constructor, but only via the writer method (here, an internal method called _writer_gi is created).

Related

Perl Moose, How to initialize a instance attribute that is Hash

What I am tring to do is the following:
I am writing a perl Moose Class and I want ot have a class attribute that is an Hash and is initialized to default values upon building.
My attempt:
has sweep_prop_configuration => (
is=>'rw',
isa => 'Hash',
reader => 'sweep_prop_configuration',
writer => '_sweep_prop_configuration',
builder => '_build_sweep_prop_configuration',
predicate => 'has_sweep_prop_configuration',
);
sub _build_sweep_prop_configuration {
my $self = shift;
my %hash;
$hash{point_number}=0;
$hash{number_of_sweep}=0;
$hash{backwards}=-1;
$hash{at_end}=-1;
$hash{at_end_val}=0;
$hash{save_all}=-1;
return %hash;
}
I am new to Moose and perl in general, excuse me if I missed something in the documentation.
Moose doesn't define Hash as a type (see Moose::Manual::Types).
It defines HashRef, though. In order to use it, change the builder's last line to
return \%hash
and change the type constraint to
isa => 'HashRef',
It still defines an instance attribute, not a class attribute. To define class attributes, use MooseX::ClassAttribute.

Writing to read-only attributes inside a Perl Moose class

Using Perl and Moose, object data can be accessed in 2 ways.
$self->{attribute} or $self->attribute()
Here is a simple example demonstrating both:
# Person.pm
package Person;
use strict;
use warnings;
use Moose;
has 'name' => (is => 'rw', isa => 'Str');
has 'age' => (is => 'ro', isa => 'Int');
sub HAPPY_BIRTHDAY {
my $self = shift;
$self->{age}++; # Age is accessed through method 1
}
sub HAPPY_BIRTHDAY2 {
my $self = shift;
my $age = $self->age();
$self->age($age + 1); # Age is accessed through method 2 (this will fail)
}
1;
# test.pl
#!/usr/bin/perl
use strict;
use warnings;
use Person;
my $person = Person->new(
name => 'Joe',
age => 23,
);
print $person->age()."\n";
$person->HAPPY_BIRTHDAY();
print $person->age()."\n";
$person->HAPPY_BIRTHDAY2();
print $person->age()."\n";
I know that when you are outside of the Person.pm file it is better to use the $person->age() version since it prevents you from making dumb mistakes and will stop you from overwriting a read only value, but my question is...
Inside of Person.pm is it best to use $self->{age} or $self->age()? Is it considered bad practice to overwrite a read-only attribute within the module itself?
Should this attribute be changed to a read/write attribute if its value is ever expected to change, or is it considered acceptable to override the read-only aspect of the attribute by using $self->{age} within the HAPPY_BIRTHDAY function?
When using Moose, the best practice is to always use the generated accessor methods, even when inside the object's own class. Here are a few reasons:
The accessor methods may be over-ridden by a child class that does something special. Calling $self->age() assures that the correct method will be called.
There may be method modifiers, such as before or after, attached to the attribute. Accessing the hash value directly will skip these.
There may be a predicate or clearer method attached to the attribute (e.g. has_age). Messing with the hash value directly will confuse them.
Hash keys are subject to typos. If you accidentally say $self->{aeg} the bug will not be caught right away. But $self->aeg will die since the method does not exist.
Consistency is good. There's no reason to use one style in one place and another style elsewhere. It makes the code easier to understand for newbs as well.
In the specific case of a read-only attribute, here are some strategies:
Make your objects truly immutable. If you need to change a value, construct a new object which is a clone of the old one with the new value.
Use a read-only attribute to store the real age, and specify a private writer method
For example:
package Person;
use Moose;
has age => ( is => 'ro', isa => 'Int', writer => '_set_age' );
sub HAPPY_BIRTHDAY {
my $self = shift;
$self->_set_age( $self->age + 1 );
}
Update
Here's an example of how you might use a lazy builder to set one attribute based on another.
package Person;
use Moose;
has age => ( is => 'rw', isa => 'Int', lazy => 1, builder => '_build_age' );
has is_baby => ( is => 'rw', isa => 'Bool', required => 1 );
sub _build_age {
my $self = shift;
return $self->is_baby ? 1 : 52
}
The lazy builder is not called until age is accessed, so you can be sure that is_baby will be there.
Setting the hash element directly will of course skip the builder method.
I don't think $self->{age} is a documented interface, so it's not even guaranteed to work.
In this case I'd use a private writer as described in https://metacpan.org/pod/Moose::Manual::Attributes#Accessor-methods:
has 'weight' => (
is => 'ro',
writer => '_set_weight',
);
You could even automate this using 'rwp' from https://metacpan.org/pod/MooseX::AttributeShortcuts#is-rwp:
use MooseX::AttributeShortcuts;
has 'weight' => (
is => 'rwp',
);
Out-of-the-box perl isn't type safe and doesn't have much in the way of encapsulation, so it's easy to do reckless things. Moose imposes some civilization on your perl object, exchanging security and stability for some liberty. If Moose gets too stifling, the underlying Perl is still there so there are ways to work around any laws the iron fist of Moose tries to lay down.
Once you have wrapped your head around the fact that you have declared an attribute read-only, but you want to change it, even though you also said you wanted it to be read-only, and in most universes you declare something read only because you don't want to change it, then by all means go ahead and update $person->{age}. After all, you know what you are doing.

Moose how to change the attribute value only when it is $undef?

Now have:
has 'id' => (
is => 'rw',
isa => 'Str',
default => sub { "id" . int(rand(1000))+1 }
);
Works OK, the:
PKG->new(id => 'some'); #the id is "some"
PKG->new() #the id is #id<random_number>
In the next scenario:
my $value = undef;
PKG->new(id => $value);
(of course) got an error:
Attribute (id) does not pass the type constraint because: Validation failed for 'Str' with value undef at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-thread-multi-2level/Moose/Exception.pm line 37
The question is:
How to achieve changing the value after it is set to undef (and only when it is $undef)? So,
has 'id' => (
is => 'rw',
isa => 'Str|Undef', #added undef to acceptable Type
default => sub { "id" . int(rand(1000))+1 }
);
Now, it accepting the $undef, but I don't want $undef but want "id" . int(rand(1000))+1. How to change the attribute value after it is set?
The after is called only for the accessors not for constructors. Maybe some weird coercion from Undef to Str - but only for this one attribute?
Ps: using the PKG->new( id => $value // int(rand(10000)) ) is not an acceptable solution. The module should accept the $undef and should silently change it to the random number.
Type::Tiny has as one of its aims to make it easy to add coercions to individual attributes really easy. Here's an example:
use strict;
use warnings;
{
package Local::Test;
use Moose;
use Types::Standard qw( Str Undef );
my $_id_default = sub { "id" . int(rand(1000)+1) };
has id => (
is => 'rw',
isa => Str->plus_coercions(Undef, $_id_default),
default => $_id_default,
coerce => 1,
);
__PACKAGE__->meta->make_immutable;
}
print Local::Test->new(id => 'xyz123')->dump;
print Local::Test->new(id => undef)->dump;
print Local::Test->new->dump;
You could also look at MooseX::UndefTolerant which makes undef values passed to the constructor act as if they were entirely omitted. This won't cover passing undef to accessors though; just constructors.
Here is an alternative, using Moose' BUILD method, which is called after an object is created.
#!/usr/bin/perl
package Test;
use Moose;
has 'id' => (
is => 'rw',
isa => 'Str|Undef',
);
sub BUILD {
my $self = shift;
unless($self->id){
$self->id("id" . (int(rand(1000))+1));
}
}
1;
package Main;
my $test = Test->new(id => undef);
print $test->id; ###Prints random number if id=> undef
More info on BUILD here:
http://metacpan.org/pod/Moose::Manual::Construction#BUILD
#choroba in a comment mentioned about the triggers. Based on this, found a next solution. The trigger is called twice in the case id=>undef, but otherwise it works.
use Modern::Perl;
package My;
use namespace::sweep;
use Moose;
my $_id_default = sub { "id" . int(rand(100_000_000_000)+1) };
my $_check_id = sub { $_[0]->id(&$_id_default) unless $_[1] };
has id => (
is => 'rw',
isa => 'Str|Undef',
default => $_id_default,
trigger => $_check_id,
);
__PACKAGE__->meta->make_immutable;
package main;
say My->new->id;
say My->new(id=>'aaa')->id;
say My->new(id=>undef)->id;

MooseX::Types and coercion error

As continue of this answer wow im fighting with the my own Moose "type library" - so trying to use "MooseX::Types".
Based on the above MooseX::Types docs, and "hoobs" comment to the above answer, I defined my own "types" as next:
package MyTypes;
use 5.016;
use Moose;
use MooseX::Types -declare => [qw( Dir File )];
use MooseX::Types::Moose qw( Str );
use Path::Class::Dir;
use Path::Class::File;
class_type Dir, { class => 'Path::Class::Dir' };
coerce Dir, from Str, via { Path::Class::Dir->new($_) };
class_type File, { class => 'Path::Class::File' };
coerce File, from Str, via { Path::Class::File->new($_) };
1;
and used it in my package
package MyDir;
use Moose;
use warnings;
use MyTypes qw(Dir); #to get the Dir type and its coercion
has 'path' => (
is => 'ro',
isa => Dir, # Dir is defined in the package MyTypes
required => 1,
);
1;
and tried with the next short script
use 5.016;
use warnings;
use MyDir;
my $d = MyDir->new(path => "/tmp");
Error:
Attribute (path) does not pass the type constraint because: Validation failed for 'MyTypes::Dir' with value /tmp (not isa Path::Class::Dir) at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Moose/Meta/Attribute.pm line 1279.
Moose::Meta::Attribute::verify_against_type_constraint(Moose::Meta::Attribute=HASH(0x7f9e9b1c2618), "/tmp", "instance", MyDir=HASH(0x7f9e9b826bb8)) called at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Moose/Meta/Attribute.pm line 1266
Moose::Meta::Attribute::_coerce_and_verify(Moose::Meta::Attribute=HASH(0x7f9e9b1c2618), "/tmp", MyDir=HASH(0x7f9e9b826bb8)) called at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Moose/Meta/Attribute.pm line 536
Moose::Meta::Attribute::initialize_instance_slot(Moose::Meta::Attribute=HASH(0x7f9e9b1c2618), Moose::Meta::Instance=HASH(0x7f9e9b1c3588), MyDir=HASH(0x7f9e9b826bb8), HASH(0x7f9e9b826a98)) called at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Class/MOP/Class.pm line 525
Class::MOP::Class::_construct_instance(Moose::Meta::Class=HASH(0x7f9e9b9e6990), HASH(0x7f9e9b826a98)) called at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Class/MOP/Class.pm line 498
Class::MOP::Class::new_object(Moose::Meta::Class=HASH(0x7f9e9b9e6990), HASH(0x7f9e9b826a98)) called at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Moose/Meta/Class.pm line 284
Moose::Meta::Class::new_object(Moose::Meta::Class=HASH(0x7f9e9b9e6990), HASH(0x7f9e9b826a98)) called at /Users/me/perl5/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/darwin-2level/Moose/Object.pm line 28
Moose::Object::new("MyDir", "path", "/tmp") called at t.pl line 5
So, doesn't accept the 'Str' and don't do the coercion.
What is wrong in the above few lines? I'm pretty sure than it is really very small bug, because i followed the MooseX::Types docs (at least i hope) - but unable to find the error.
I'm starting be really hopeless with Moose, please HELP...
Ps: My goal is defining all my own "types" in one place (package) and use it everywhere where i need them with one single "use...".
You need to tell Moose that it's OK to use coercion on that attribute. You do this by adding coerce into the attribute definition:
has 'path' => (
is => 'ro',
isa => Dir, # Dir is defined in the package MyTypes
required => 1,
coerce => 1,
);

Perl Moose TypeDecorator error. How do I debug?

I've recently run into a problem I'd greatly appreciate any insight into. I posted a similar question prior to Christmas over at PerlMonks with some feedback to switch away from MooseX::Declare ([http://www.perlmonks.org/?node_id=877703][1]). I have now switched the code over to vanilla Moose with MooseX::Types and MooseX::Params::Validate. However, the same error is occurring in the same spot. Not surprising since it appears to be MooseX::Types related.
I am getting the following error (tried to space this out for readability
and bottom of stack truncated):
plxc16479> tmp10.pl
Argument cannot be 'name' at /nfs/pdx/disks/nehalem.pde.077/perl/lib64/site_perl/MooseX/Types/TypeDecorator.pm line 88
MooseX::Types::TypeDecorator::new('MooseX::Types::TypeDecorator=HASH(0x1620c58)', 'name', 'g1145114N5582201_16161616a2x_FU02xxT_2bxc2e3_6x0xxxp0fx0xxx0x...', 'mask_data', '', 'tags', 0) called at /nfs/pdx/disks/nehalem.pde.077/projects/lib/Program-Plist-Pl/lib/Program/Plist/Pl.pm line 61
Program::Plist::Pl::_create_pattern_obj(undef, 'name', 'g1145114N5582201_16161616a2x_FU02xxT_2bxc2e3_6x0xxxp0fx0xxx0x...', 'mask_data', '', 'tag_data', '') called at /nfs/pdx/disks/nehalem.pde.077/projects/lib/Program-Plist-Pl/lib/Program/Plist/Pl.pm line 77
Program::Plist::Pl::BUILD('Program::Plist::Pl=HASH(0x162d6c0)', 'HASH(0x162d648)') called at generated method (unknown origin) line 101
Program::Plist::Pl::new('Program::Plist::Pl', 'name', 'bist_hfmmin_16161616_list', 'parents', 'HASH(0xccf040)', 'fh', 'GLOB(0xccc928)', 'external_pl_code', 'CODE(0x14910b0)', ...) called at /nfs/pdx/disks/nehalem.pde.077/projects/lib/Program-Roles-PlHandler/lib/Program/Roles/PlHandler.pm line 52
Program::Roles::PlHandler::_create_global_pl_obj(undef, 'name', 'bist_hfmmin_16161616_list', 'parents', 'HASH(0xccf040)', 'fh', 'GLOB(0xccc928)') called at /nfs/pdx/disks/nehalem.pde.077/projects/lib/Program-Plist-Pl/lib/Program/Plist/Pl.pm line 77
Program::Plist::Pl::BUILD('Program::Plist::Pl=HASH(0xccd300)', 'HASH(0xccc628)') called at generated method (unknown origin) line 101
Program::Plist::Pl::new('Program::Plist::Pl', 'name', 'bist_list', 'parents', 'HASH(0xccce80)', 'fh', 'GLOB(0xccc928)', 'external_pl_code', 'CODE(0x14910b0)', ...) called at /nfs/pdx/disks/nehalem.pde.077/projects/lib/Program-Roles-PlHandler/lib/Program/Roles/PlHandler.pm line 52
The problem i seems to be the top call to TypeDecorator::new. The TypeDecorator constructor seems to expect two arguments, the class/self argument and a reference to a TypeDecorator or TypeConstraint object. Instead, it's somehow receiving the arguments from my create pattern object call. I have verified that the arguments coming into the _create_pattern_obj function are correct and that the arguments going into the Pattern->new call are also correct (borne out by the stack traced arguments). The _create_pattern_obj function looks like this:
sub _create_pattern_obj {
my ($self, $name, $mask_data, $tag_data) = validated_list(\#_,
name => {isa => Str},
mask_data => {isa => Str, optional => 1},
tag_data => {isa => Str, optional => 1});
$mask_data = '' if !defined $mask_data;
my $tags = defined $tag_data ? map {$_ => 1} split(',', $tag_data) : {};
my $pattern_obj = Program::Plist::Pl::Pattern->new(name => $name,
mask_data => $mask_data,
tags => $tags);
$self->_add_pattern($pattern_obj);
}
The function is dieing on the Program::Plist::Pl::Pattern->new call, which is the line 61
in file Pl.pm file referenced in the above call stack where the TypeDecorator::new call is claiming to come from.
The Pattern class is:
package Program::Plist::Pl::Pattern;
use 5.012002;
our $VERSION = sprintf "2.%03d", q($Revision: 473 $) =~ /: (\d+)/;
use Moose;
use namespace::autoclean;
use MooseX::Types::Moose qw(Str Num Int HashRef);
use MooseX::Params::Validate;
has 'name' => (isa => Str,
is => 'ro',
required => 1);
has 'tuple' => (isa => Int,
is => 'ro');
has 'tid' => (isa => Int,
is => 'ro');
has 'weight' => (isa => Num,
is => 'ro');
has 'tags' => (isa => HashRef[Str],
is => 'ro',
default => sub {{}});
has 'mask_data' => (isa => Str,
is => 'rw',
default => '',
writer => '_set_mask_data');
sub has_tag {
my ($self, $tag) = (shift,
pos_validated_list(\#_, {isa => Str}));
exists $self->{tags}->{$tag} ? return 1 : return 0;
}
sub _add_tag {
my ($self, $tag) = (shift,
pos_validated_list(\#_, {isa => Str}));
$self->{tags}->{$tag} = 1;
}
sub BUILDARGS {
print STDERR 'CALLED '.__PACKAGE__."BUILDARGS\n";
print STDERR 'ARGUMENTS:'.join(',', #_)."\n";
}
sub BUILD {
my ($self) = #_;
print STDERR 'CALLED '.__PACKAGE__."::BUILD\n";
}
__PACKAGE__->meta->make_immutable;
1;
Somehow, judging from the arguments in the call stack, my arguments to the Pattern->new call are ending up being passed to the TypeDecorator::new call and it's choking on them. I've verified that a good call to this subroutine (from and earlier stack trace) looks like this (note the two arguments):
DB<1> T
$ = MooseX::Types::TypeDecorator::new('MooseX::Types::TypeDecorator', ref(Moose::Meta::TypeConstraint)) called from file `/nfs/pdx/disks/nehalem.pde.077/perl/lib64/site_perl/MooseX/Types.pm' line 464
The problem is that I can't figure out how to debug what's going on. When stepping through the code, execution passes directly from the Pattern->new call to the TypeDecorator code. This is occurring prior to any of my class code executing. I know Moose is creating the new method for me, but I can't figure out how to debug code I can't look at.
I've looked through documentation on Moose, but that's all on how to use it as opposed to what's going on under the hood. I did read through the Class::MOP documentation, but I'm unclear as to exactly where this code is being created and when. While I've learned a fair bit from all my research, none of it has directly help me with my problem :)
First off, any ideas as to what's occurring would be appreciated. Second, how do I debug into this issue? All my usual debug tools have failed me! The execution is jumping directly from my new call to the problem code and I can't seem to trace where the TypeDecorator::new arguments are actually being passed from. Lastly, are there any good writeups out there on exactly how Moose does what it does? Or Class::MOP?
Edit - Here are my type definitions. I might add this is my first foray into Moose, so if you see anything I'm doing that's odd feel free to point it out.
package Program::Types;
use 5.012002;
use strict;
use warnings;
our $VERSION = sprintf "2.%03d", q($Revision: 473 $) =~ /: (\d+)/;
# predeclare types
use MooseX::Types
-declare => [qw(NonemptyStr FilePath DirectoryPath FilePathThatExists DirectoryPathThatExists
TwoDigNum Pl LocalPl Pattern Program_Env Program_Whichload Program_Tpl
Program_Plist Program_Bmfc Program_Tpl_Test Program_Tpl_Flow
Program_Tpl_Flow_Item Program_Tpl_Flow_Item_Result Word)];
# import some MooseX builtin types that will be built on
use MooseX::Types::Moose qw(Str Int Object);
# types base on some objects that I use
class_type Pl, {class => 'Program::Plist::Pl'};
class_type LocalPl, {class => 'Program::Plist::LocalPl'};
class_type Pattern, {class => 'Program::Plist::Pl::Pattern'};
class_type Program_Env, {class => 'Program::Env'};
class_type Program_Whichload, {class => 'Program::Whichload'};
class_type Program_Tpl, {class => 'Program::Tpl'};
class_type Program_Tpl_Test, {class => 'Program::Tpl::Test'};
class_type Program_Tpl_Flow, {class => 'Program::Tpl::Flow'};
class_type Program_Tpl_Flow_Item, {class => 'Program::Tpl::Flow::Item'};
class_type Program_Tpl_Flow_Item_Result, {class => 'Program::Tpl::Flow::Item::Result'};
class_type Program_Plist, {class => 'Program::Plist'};
class_type Program_Bmfc, {class => 'Program::Bmfc'};
subtype Word,
as Str,
where {$_ =~ /^\w*$/};
coerce Word,
from Str,
via {$_};
subtype NonemptyStr,
as Str,
where {$_ ne ''};
coerce NonemptyStr,
from Str,
via {$_};
subtype TwoDigNum,
as Int,
where {$_ =~ /^\d\d\z/},
message {'TwoDigNum must be made of two digits.'};
coerce TwoDigNum,
from Int,
via {$_};
subtype FilePath,
as Str,
where {!($_ =~ /\0/)},
message {'FilePath cannot contain a null character'};
coerce FilePath,
from Str,
via {$_};
subtype DirectoryPath,
as Str,
where {!($_ =~ /\0/)},
message {'DirectoryPath cannot contain a null character'};
coerce DirectoryPath,
from Str,
via {$_};
subtype FilePathThatExists,
as Str,
where {(!($_ =~ /\0/) and -e $_)},
message {'FilePathThatExists must reference a path to a valid existing file.'.
"Path ($_)"};
coerce FilePathThatExists,
from Str,
via {$_};
coerce FilePathThatExists,
from FilePath,
via {$_};
subtype DirectoryPathThatExists,
as FilePath,
where {(!($_ =~ /\0/) and -d $_)},
message {'DirectoryPathThatExists must reference a path to a valid existing '.
"directory. Path ($_)"};
coerce DirectoryPathThatExists,
from Str,
via {$_};
coerce DirectoryPathThatExists,
from DirectoryPath,
via {$_};
1;
Edit2 -- Removed due to obvious operator error :) Note that I am using BUILDARGS in the Pattern class without returning the argument list. I have removed this in current code with no change to the error.
Phaylon, Here's the Program::Plist::Pl class.
package Program::Plist::Pl;
use 5.012002;
our $VERSION = sprintf "2.%03d", q($Revision: 473 $) =~ /: (\d+)/;
use Moose;
use namespace::autoclean;
use Program::Plist::Pl::Pattern;
use Program::Types qw(Pl LocalPl TwoDigNum Pattern);
use Program::Utils qw(rchomp);
use MooseX::Types::Moose qw(HashRef GlobRef Str);
use MooseX::Params::Validate;
with 'Program::Roles::PlHandler';
has 'name' => (isa => Str,
is => 'ro',
required => 1);
has 'parents' => (isa => HashRef[Pl|LocalPl],
is => 'ro',
required => 1);
has 'children' => (isa => HashRef[Pl|LocalPl],
is => 'ro');
has 'prefixes' => (isa => HashRef[TwoDigNum],
is => 'ro',
default => sub{{}});
has 'patterns' => (isa => HashRef[Pattern],
is => 'ro',
default => sub{{}});
sub _add_child {
my ($self, $obj) = (shift,
pos_validated_list(\#_, {isa => Pl|LocalPl}));
$self->{children}->{$obj->name} = $obj;
}
sub _add_pattern {
my ($self, $obj) = (shift,
pos_validated_list(\#_, {isa => Pattern}));
$self->{patterns}->{$obj->name} = $obj;
}
sub _create_pattern_obj {
$DB::single = 1;
my ($self, $name, $mask_data, $tag_data) = validated_list(\#_,
name => {isa => Str},
mask_data => {isa => Str, optional => 1},
tag_data => {isa => Str, optional => 1});
$mask_data = '' if !defined $mask_data;
my $tags = defined $tag_data ? map {$_ => 1} split(',', $tag_data) : {};
$DB::single = 1;
my $pattern_obj = Program::Plist::Pl::Pattern->new(name => $name,
mask_data => $mask_data,
tags => $tags);
$self->_add_pattern($pattern_obj);
}
sub BUILD {
my ($self, $fh) = (shift,
pos_validated_list([$_[0]->{fh}], {isa => GlobRef}));
while (<$fh>) {
# skip empty or commented lines
rchomp;
next if ((/^\s*#/) or (/^\s*$/));
# handle global plist declarations
if (my #m = /^\s*GlobalPList\s+(\w+)/) {
# creating new object and adding it to our data print STDERR
# "SELF($self)\n".join("\n",sort keys
# %Program::Plist::Pl::)."\n";
$self->_create_global_pl_obj(name => $m[0],
parents => {%{$self->parents},
$self->name => $self},
fh => $fh);
}
# handle local referenced plist declarations
elsif (#m = /^\s*PList\s+(\w+):(\w+)/) {
$self->_create_local_pl_obj(file => $m[0],
name => $m[1]);
}
# handling pattern lines
elsif (#m = /^\s*Pat\s+(\w+)\s*(\[.*\])?\s*;\s*(#([\w,])#)?\s*$/) {
$self->_create_pattern_obj(name => $m[0],
mask_data => do {defined $m[1] ? $m[1] : ''},
tag_data => do {defined $m[2] ? $m[2] : ''});
}
# handling our patlist closure
elsif (/^\s*\}/) {
last;
}
}
# need to populate our hash of child plists
for (#{$self->data}) {
if (($_->isa('Pl')) or ($_->isa('LocalPl'))) {
$self->_add_child($_);
}
}
}
__PACKAGE__->meta->make_immutable;
1;
The problem is I believe here.
use Program::Types qw(Pl LocalPl TwoDigNum Pattern);
You're importing a function named Pattern into your Program::Plist::Pl class. You then call this function (unintentionally) here:
my $pattern_obj = Program::Plist::Pl::Pattern->new(name => $name,
mask_data => $mask_data,
tags => $tags);
Specifically Program::Plist::Pl::Pattern resolves to your fully qualified function name rather than to the Class (technically package name) you're expecting. This function returns a TypeObject which you then call new() on.
Note: This is exactly what phaylon suggests in the comments above.
There really is no way to debug this except to know that you can always call a function with it's fully qualified name, and thus should never have a MooseX::Type and a valid Class name collide.
If it were me I'd start writing a very simple test case and add code to replicate the original file until it breaks. I'd probably start with the call to new. Then slowly add back assumptions until I found the one that breaks. Hopefully you add the MooseX::Types call early enough in that process that it triggers the "oh duh obviously that is it" moment.