How do you get MotherDogRobot to birth an array of puppy objects using map and a hash of hashes? - perl

Puppy meta data gets read in from config file using (General::Config) and creates this hash of hashes
$puppy_hashes = {
puppy_blue => { name => 'charlie', age => 4 },
puppy_red => { name => 'sam', age => 9 },
puppy_yellow => { name => 'jerry', age => 2 },
puppy_green => { name => 'phil', age => 5 },
}
the MotherDogRobot package consumes the puppies hash to birth an array of puppy objects (lol)
package MotherDogRobot;
use Moose;
use Puppy;
use Data::Dumper;
#moose includes warn and strict
sub init_puppy{
my($self,%options) = #_;
my $puppy = Puppy->new( %options );
return ($puppy);
}
sub birth_puppies{
my($self,$puppy_hashes) = #_;
my #keys = keys %{$puppy_hashes};
my #puppies = map { $self->init_puppy( $puppy_hashes->{$_} ) } #keys;
return(#puppies);
}
sub show_me_new_puppies{
my($self,$puppy_hashes) #_;
print Dumper($self->birth_puppies($puppy_hashes));
}
Error odd number of arguments
passing %options to Puppy->new(%options)
no luck birthing puppies -- which means I can't put lasers on their heads =/
UPDATE
I think the problem is that I'm passing a Hash Ref to init_puppy() instead of an array or hash, so when I try to pass %options to the new constructor, it's not getting a proper ( key => value) pair -- hence the odd number of arguments error.
But from this standpoint I've been looking at this code too long I cant figure out how to dereference this properly.
btw this is my official day 22 of using Perl!

you're using empty variables as if they're not empty, that is, you're not doing anything at all
print "hi $_ " for my #foo;
This assumes that the incomplete snippet you've shown is what you're really using
update: Similarly in sub init_puppy, you never initialize my($self,%options)=#_;
#!/usr/bin/perl --
use strict;
use warnings;
Main( #ARGV );
exit( 0 );
sub Main {
my $puppy_hashes = {
puppy_blue => { name => 'charlie', age => 4 },
puppy_red => { name => 'sam', age => 9 },
puppy_yellow => { name => 'jerry', age => 2 },
puppy_green => { name => 'phil', age => 5 },
};
for my $puppy ( MotherDogRobot->birth_puppies($puppy_hashes) ) {
print join ' ', $puppy, $puppy->name, $puppy->age, $puppy->dump, "\n";
}
}
BEGIN {
package Puppy;
BEGIN { $INC{'Puppy.pm'} = __FILE__; }
use Any::Moose;
has 'name' => ( is => 'rw', isa => 'Str' );
has 'age' => ( is => 'rw', isa => 'Int' );
package MotherDogRobot;
BEGIN { $INC{'MotherDogRobot.pm'} = __FILE__; }
use Moose;
use Puppy;
sub init_puppy {
my ( $self, %options ) = #_;
my $puppy = Puppy->new(%options);
return ($puppy);
}
sub birth_puppies {
my ( $self, $puppy_hashes ) = #_;
my #puppies = map { $self->init_puppy( %{$_} ) } values %$puppy_hashes;
return (#puppies);
}
no Moose;
}

The standard Moose constructor will accept both
->new( %{ $puppy_hashes->{$_} } )
and
->new( $puppy_hashes->{$_} )
if $puppy_hashes contains what you say it does, and $_ is an existing key.
Furthermore, Moose will not give the error Error odd number of argments when you pass no arguments. (You're not assigning anything to %config.)
I can't tell which part of what you said is wrong, but what you said doesn't add up.

Related

Better way to get all attributes from a Moose class as a hash

I want to get all attributes back from a class as a hash.
Is there any better way to do it than this ?
Ideally(?) I would like to be able to say something like:
my $hash = \%{ Diag->new( {range =>1, code => 'AB'} ) };
But will settle for:
my $d = Diag->new( {range =>1, code => 'AB'} );
my $hash = $d->hash;
package Diag;
use Moose;
my #attrs = qw/range code severity source message/;
has 'range' => ( is => 'rw', isa => 'Int' );
has 'code' => ( is => 'rw', isa => 'String' );
has 'severity' => ( is => 'rw', isa => 'Int' );
has 'source' => ( is => 'rw', isa => 'String' );
has 'message' => ( is => 'rw', isa => 'String' );
sub hash {
my $self = shift;
my $hash = {};
for (#attrs) {
$hash->{$_} = $self->$_;
}
return $hash;
}
no Moose;
1;
EDIT Hash with string output for pack/unpack:
# Combining this attribute and the record_format would be great.
# if $self->record->format worked that would be cool.
has 'record' => (
is => 'ro',
isa => 'HashRef',
default => sub {
{
foo => 'A5',
foo2 => 'A16',
}
);
sub record_format
{
my $self = shift;
my #fields = qw( foo foo2 );
return _build_format_string($self->record, \#fields);
}
sub _build_format_string {
return join '', map { $_[1]->{$_} } #{ $_[2] };
}
EDIT2
I found that if I created an Attribute Trait I could make this a little nicer. This way the hash order is with the attribute and only one format method is needed.
package Order;
use Moose::Role;
has order => (
is => 'ro',
isa => 'ArrayRef',
predicate => 'has_order',
);
Moose::Util::meta_attribute_alias('Order');
1;
package Record;
use Moose;
has 'record' => (
traits => [qw/Order/],
is => 'ro',
isa => 'HashRef',
default => sub {
{
foo => 'A5',
foo2 => 'A16',
},
;
},
order => [qw(foo foo2)]
);
sub format {
my ( $self, $attr ) = #_;
my $fields = $self->meta->get_attribute($attr)->order();
return join '', map { $self->{$attr}{$_} } #$fields;
}
1;
my $r = Record->new();
print $r->format("record");
Outputs: A5A16
I would much rather pack that into a method, but your "ideal" case is almost there
my $data = { %{ Diag->new( {range =>1, code => 'AB'} ) } };
The %{...} returns a (key,value,...) list so you want {} to make a hashref out of it, not \ (which curiously turns it back into an object).
But really, that should be tucked away in a method
my $data = Diag->new(...)->get_data;
package Diag;
...
sub get_data { return { %{$_[0]} } };
...
1;
For purely presentational purposes – to print them out – consider using a module, so you don't have to worry about (or know) which attributes have what reference as a value. I use Data::Dump for conciseness of its output
my $obj = Diag->new(...);
say $obj->stringify(); # whole object serialized
say for $obj->stringify('attr1', 'attr1', ...); # serialized values for each
package Diag;
...
use Data::Dump qw(pp);
...
sub stringify {
my $self = shift;
return map { pp $self->{$_} } #_ if #_;
return { pp %$self } }
}
If native OO is used and not Moo/Moose also overload "" for say $obj; use
use overload q("") => sub { return shift->stringify() }
In Moo and Moose the stringification of object under "" (implied in prints as well) is provided.
By further clarifications the code below doesn't address the actual problem. I'll edit but I am leaving this for now as it was deemed generally useful.
It has come up in comments and question edit that a part of the intent is to be able to retrieve values for attribute(s) as well, and packed. The added code does that, but as there is explicit dereferencing a check with ref should be added so to correctly retrieve all values, from either of arrayref, hashref, or string/number. For example
sub record_format {
my ($self, #attrs) = #_;
#attrs = qw(attr1 attr2 ...) if not #attrs; # default list
my $packed;
foreach my $attr (#attrs) {
my $val = $self->{$attr};
my $rv = ref $val;
if (not $rv) { $packed .= $val }
elsif ($rv eq 'HASH') { $packed .= join '', values %$val }
elsif ($rv eq 'ARRAY') { $packed .= join '', #$val }
}
return $packed;
}
This packs values of the passed attributes or of the listed defaults.
The desired $self->record->format can't work nicely since $self->record doesn't return an object so you can't string another method call. You can write an accessor but if you made it return an object under any circumstances that would likely be a surprising behavior, thus not good design.

How can I extract all global variables from a script and get each data type in Perl?

I like to capture all global variables from an external Perl script with Perl. Currently I am hanging around the type detection.
How to determine the correct data type ('', 'SCALAR', 'HASH', 'ARRAY', 'CODE')?
Parser script:
my %allVariables = ();
{
do "scriptToBeParsed.pl";
foreach my $sym ( keys %main:: ) {
# Get all normal variables and scalar/hash/array references:
if ( ref( *{"$sym"} ) =~ m/^(?:|SCALAR|HASH|ARRAY)$/ ) {
$allVariables{"$sym"} = *{"$sym"};
}
}
}
Script to be parsed:
$someVariable1 = 'Yes, I like to be captured';
$otherVariable2 = \'And I also want to be captured';
%anotherVariable3 = ( 'Capture' => 'me' );
#lameVariable4 = ( 'Capture', 'me' );
$fooVariable5 = { 'Capture' => 'me' };
$barVariable6 = [ 'Capture', 'me' ];
$subVariable7 = sub { return "Don't capture me!" };
sub dontCaptureMe { return "Don't capture me!" }
In my example ref( *{"$sym"} ) returns always 'GLOB' (of course).
Another approach would be to use the has-like access of the typeglob, which is explained in Chapter 8 of brian d foy's Mastering Perl on page 131f.
package test;
no strict;
no warnings;
$someVariable1 = 'Yes, I like to be captured';
$otherVariable2 = \'And I also want to be captured';
%anotherVariable3 = ( 'Capture' => 'me' );
#lameVariable4 = ( 'Capture', 'me' );
$fooVariable5 = { 'Capture' => 'me' };
$barVariable6 = [ 'Capture', 'me' ];
$subVariable7 = sub { return "Don't capture me!" };
sub dontCaptureMe { return "Don't capture me!" }
say $dontCaptureMe;
my %allVariables = ();
{
do "scriptToBecomeParsed.pl";
foreach my $sym ( keys %test:: ) {
for (qw( SCALAR HASH ARRAY CODE IO)) {
if (*{"$sym"}{$_}) {
$allVariables{$_}->{"$sym"} = *{"$sym"}{$_};
}
}
}
}
print Data::Dumper::Dumper \%allVariables;
This will produce the following output:
$VAR1 = {
'CODE' => {
'dontCaptureMe' => sub { "DUMMY" }
},
'ARRAY' => {
'lameVariable4' => [
'Capture',
'me'
]
},
'HASH' => {
'anotherVariable3' => {
'Capture' => 'me'
}
},
'SCALAR' => {
'someVariable1' => \'Yes, I like to be captured',
'__ANON__' => \undef,
'subVariable7' => \sub { "DUMMY" },
'dontCaptureMe' => \undef,
'otherVariable2' => \\'And I also want to be captured',
'BEGIN' => \undef,
'barVariable6' => \[
'Capture',
'me'
],
'anotherVariable3' => \undef,
'lameVariable4' => \undef,
'fooVariable5' => \{
'Capture' => 'me'
}
}
};
like you said
ref( *{"$sym"} ) returns always 'GLOB' (of course).
Because perl stores everything in the symbol table in a glob, it is impossible to tell which data type something is. This is because in perl it is perfectly valid to have an array, scalar, hash or whatever else with the same name... because of this, perl stores everything in globs to avoid collisions. What you could do is loop through all of the symbols in the symbol table and test each glob against all the possible things that it could be (the set isn't too large) and see which ones are set.
Alternatively, a more practical approach might be to just load the perl script as text and parse for $, %, #, sub, open (filehandle) to see what type everything is.

Moose around method modifier, setter and constructor (new): intercept all updates to an attribute

Update
The code I posted in my original question was illustrative of the way method modifier do or don't work.
It was not necessarily illustrative of the problem description I gave.
This code should be. It works, but contains a hack in the trigger I used to code the requirement of tracking all updates and acting upon them based on the value supplied to the setter.
package Article;
use Moose;
use Moose::Util::TypeConstraints;
has 'name', is => 'rw', isa => 'Str', required => 1;
has 'price', is => 'rw', isa => 'Num', required => 1;
has 'quantity', is => 'rw', isa => 'Num', required => 1,
trigger => \&update_quantity;
has 'quantity_original', is => 'rw', isa => 'Num',
predicate => 'quantity_fix',
clearer => 'quantity_back_to_normal';
# https://metacpan.org/module/Moose::Cookbook::Basics::Recipe3
# A trigger accepts a subroutine reference, which will be called as a method
# whenever the attribute is set. This can happen both during object
# construction or later by passing a new object to the attribute's accessor
# method. However, it is not called when a value is provided by a default or
# builder.
sub update_quantity {
my( $self, $val ) = #_;
# print STDERR $val, "\n";
if ( $val == int $val ) {
$self->quantity_back_to_normal;
} else {
$self->quantity_original( $val );
# Updating quantity via setter would retrigger this code.
# Which would defeat its purpose. The following won't:
$self->{quantity} = 1; # hack, yes; but it does work
}
}
around name => sub {
my $orig = shift;
my $self = shift;
return $self->$orig( #_ ) if #_; # setter
return $self->$orig unless $self->quantity_fix;
return sprintf '%s (%s)', $self->$orig, $self->quantity_original;
};
around price => sub {
my $orig = shift;
my $self = shift;
return $self->$orig( #_ ) if #_; # setter
return $self->$orig unless $self->quantity_fix;
return int( 100 * $self->$orig * $self->quantity_original + 0.5 ) / 100;
};
__PACKAGE__->meta->make_immutable; no Moose;
package main;
use Test::More;
{ my $art = Article->new( name => 'Apfel', price => 33, quantity => 4 );
is $art->price, 33, 'supplied price';
is $art->quantity, 4, 'supplied quantity';
is $art->name, 'Apfel', 'supplied name';
}
{ my $art = Article->new( name => 'Mehl', price => 33, quantity => 4.44 );
# diag explain $art;
is $art->quantity, 1, 'has quantity fixed';
is $art->price, 33 * 4.44, 'has price fixed';
is $art->name, 'Mehl (4.44)', 'has name fixed';
# tougher testing ...
$art->quantity(3);
is $art->quantity, 3, 'supplied quantity again';
is $art->price, 33, 'supplied price again';
is $art->name, 'Mehl', 'supplied name again';
}
done_testing;
Still not sure what Moose facility to employ to do the job.
An abundance of features and facilities does not always make things easier.
At least not when you try not to reinvent any wheels and reuse what can be reused.
Original question
It appears the around method modifier isn't called as part of building the object (when calling new). Test case here:
package Bla;
use Moose;
has 'eins', is => 'rw', isa => 'Int';
has 'zwei', is => 'rw', isa => 'Num';
around [qw/ eins zwei /] => sub {
my $orig = shift;
my $self = shift;
return $self->$orig unless #_;
my $val = shift;
if ( $val == int $val ) {
return $self->$orig( $val );
}
else {
return $self->$orig( 1 );
warn "replaced $val by 1";
}
};
package main;
use Test::More;
use Test::Exception;
dies_ok { Bla->new( eins => 33.33 ) } 'dies because of Int type constraint';
my $bla = Bla->new( zwei => 22.22 );
is $bla->zwei, 22.22, 'around has not been called';
done_testing;
Let me explain what I want to achieve. There's a class that has quantity and price (and some more state). When quantity comes in (via new or the setter, I don't care), I want to make sure it ends up as an integer (hence the constraint). If it's not an integer, I want to replace it by just 1 and make some other updates to the object, like saving the original quantity and multiplying the price by the original quantity. Both for the constructor and the setter.
What should I do? Provide a subroutine that does the job and call it from both around BUILDARGS and around quantity?
How about this?
package Bla;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'MyInt',
as 'Int';
coerce 'MyInt',
from 'Num',
via { 1 };
has 'eins', is => 'rw', isa => 'Int';
has 'zwei', is => 'rw', isa => 'MyInt', coerce => 1;
package main;
use Test::More;
use Test::Exception;
dies_ok { Bla->new( eins => 33.33 ) } 'dies because of Int type constraint';
my $bla = Bla->new( zwei => 22.22 );
is $bla->zwei, 1, '22.22 -> 1';
my $bla2 = Bla->new( zwei => 41 );
is $bla2->zwei, 41, '41 -> 41';
done_testing;
When I keep running against walls, I know I did something wrong, and I'm running against walls. The design sucks. I think the key problem is that you have one field serving two purposes.
If the only purpose of orig_quantity is to normalize the price, I suggested that you normalize quantity and price after they are set. This could be done explicitly, or it could be done implicitly when you try to fetch them as shown below.
has price => (
accessor => '_price',
isa => 'Num',
handles => {
price => sub {
my $self = shift;
return $self->_price(#_) if #_;
$self->normalize();
return $self->_price();
},
},
);
has quantity => (
accessor => '_quantity',
isa => 'Num',
handles => {
quantity => sub {
my $self = shift;
return $self->_quantity(#_) if #_;
$self->normalize();
return $self->_quantity();
},
},
);
sub normalize {
my ($self) = #_;
my $quantity = $self->_quantity();
return if is_an_int($quantity);
$self->_quantity(1);
$self->_price($self->_price() / $quantity);
}
If you actually do need orig_quantity, then you probably want the constructor to set this directly and make quantity a derived value.

Perl, #array in perl constructor

I write perl classes, but I don't know how to have a array or a hash in my $this variable ?
I have a pack.pm :
#!/usr/bin/perl -w
use strict;
use Parallel::ForkManager;
package Pack;
our $cgi = new CGI;
sub new {
my ($classe, $nom, $nbports, $gio) = #_;
my $this = {
"nom" => $nom,
"nbports" => $nbports,
"gio" => $gio
};
bless($this, $classe);
return $this;
}
...
1;
I would like to have a #tab, I can access via $this->tab, but I don't want to give it in arg to the instance.
How does it work in Perl ?
Thanks.
Given your answer to my comments, I think you want
my($this) = {
"nom" => $nom,
"nbports" => $nbports,
"gio" => $gio,
"tab" => []
};
i.e. set $this->{tab} to be a reference to a new anonymous array.
Now you can reference it as you wish, e.g.
$this->{"tab"}[0] = "new value";
print "Table contains ", scalar(#{$this->{"tab"}}), "entries\n";
Consider using Moose for your OO Perl needs.
I've created a Moose version of your object that includes an attribute with an attribute featuring Array trait delegation, inlcuding currying of delegated methods. Moose offers easy ways to generate powerful, encapsulated classes without writing reams of boilerplate.
I created a class Pack with attributes: nom, nbports, gio, and tab.
nom is a read-only string and is required when the object is created.
nbports is a read-only integer value and defaults to 32 when not provided.
gio is an optional, read-write boolean value.
tab is an array of strings. All sorts of behavior has been defined for tab:
all_tabs returns a list of the contents of tabs
add_tab pushes values onto the end of tabs
tab_count returns a count of the elements in tabs
alpha_tabs returns a list of the members of tabs alphabetical order
turn_tabs returns a list of the strings in tabs, but with the letters in reverse
Any attempts to set an attribute with be checked for type correctness.
Moose creates all the required methods to support these complex behaviors with the following code:
package Pack;
use Moose;
has 'nom' => (
is => 'ro',
isa => 'Str',
required => 1,
);
has 'nbports' => (
is => 'ro',
isa => 'Int',
default => 32,
);
has 'gio' => (
is => 'rw',
isa => 'Bool',
predicate => 'has_gio',
);
has 'tab' => (
traits => ['Array'],
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub {[]},
handles => {
all_tabs => 'elements',
add_tab => 'push',
turn_tabs => [ 'map', sub { reverse } ],
tab_count => 'count',
alpha_tabs => [ 'sort', sub { lc($a) cmp lc($b) } ],
},
);
__PACKAGE__->meta->make_immutable;
no Moose;
1;
Usable like so:
my $p = Pack->new( nom => 'Roger', tab => [qw( fee fie foe fum )] );
my $gio_state = 'UNSET';
if( $p->has_gio ) {
$gio_state = $p->gio ? 'TRUE' : 'FALSE';
}
print "GIO is $gio_state\n";
my #turned = $p->turn_tabs; # eef eif eof muf
$p->add_tabs( 'faa', 'fim' );
my #sorted = $p->alpha_tabls; # faa fee fie fim foe fum
my $count = $p->tab_count; # 6
my $ports = $p->nbports; # 32
try with:
sub set_tab {
my ($self, #tab) = #_;
$self->{ tab } = \#tab;
}

How can I reference the object when building it with Perl's Class::Struct?

I am new to object oriented Perl and i have to access member variable of same object in another subrutine of same object. Sample code is here :
use Class::Struct;
struct Breed =>
{
name => '$',
cross => '$',
};
struct Cat =>
[
name => '$',
kittens => '#',
markings => '%',
breed => 'Breed',
breed2 => '$',
];
my $cat = Cat->new( name => 'Socks',
kittens => ['Monica', 'Kenneth'],
markings => { socks=>1, blaze=>"white" },
breed => { name=>'short-hair', cross=>1 },
** //breed2 => sub { return $cat->breed->name;}**
);
print "Once a cat called ", $cat->name, "\n";
**print "(which was a ", $cat->breed->name, ")\n";**
print "had two kittens: ", join(' and ', #{$cat->kittens}), "\n";
But i am not sure how to use that $cat->breed->name in subroutine for breed2 ? Can some one help me with this.
The problem in breed2 is that you are trying to refer to a variable that you haven't defined yet. It looks like it is the same name, but it's not the object you are creating. It's a bit of a chicken-and-egg problem.
I'm not so sure that you want an anonymous subroutine like that in that slot anyway. Are you
just trying to shorten $cat->breed->name to $cat->breed2? You can start with undef in breed2 and change its value right after the constructor since you'll have the reference to the object then. However, even if you put a subroutine there, you have to dereference it:
my $cat = Cat->new( name => 'Socks',
kittens => ['Monica', 'Kenneth'],
markings => { socks=>1, blaze=>"white" },
breed => { name=>'short-hair', cross=>1 },
breed2 => undef,
);
$cat->breed2( sub { $cat->breed->name } );
print "Once a cat called ", $cat->name, "\n";
print "(which was a ", $cat->breed2->(), ")\n";
print "had two kittens: ", join(' and ', #{$cat->kittens}), "\n";
You can't use $cat->breed->name inside the Cat constructor.
But you can define breed2() as a method after the constructor:
sub Cat::breed2 {
my ($self) = #_;
return $self->breed->name;
}
First, I'll start with several comments, then I'll get to the meat of your question.
OO Perl is a bit different than other OO systems. There is a very thin layer of basic support for OO that makes it possible to make your objects do just about anything you want. On the down side, you can make your objects do just about anything you want. Classical OO Perl involves a lot of boilerplate code, as you implement accessors and mutators for each attribute, perhaps add type checking and so forth. This has given rise to a wide variety of tools to automate the production of boilerplate code.
There are three ways that I approach OO Perl: Moose, classical hash based all hand coded, and Class::Struct. Moose is great for systems where you have complex needs, but it has a big impact on app start-up time. If launch time is important for your application, Moose is, for now, out of the question. Class::Struct is a great way to get a lowest common denominator, quick, simple OO app together, on the downside it doesn't support inheritance. This is where hand coded OOP comes in. If Moose or Class::Struct aren't viable options for one reason or another, I fall back on the basics. This strategy has worked well for me. The only change I have felt the need to make over the last few years, is to add Moose to my standard toolkit. It's a welcome addition.
Damian Conway's Object Oriented Perl is an amazing book that clearly explains OOP, how OO Perl works, and how to build objects that can do amazing things. It's a bit dated, but the book still holds up. Any serious student of OO Perl should read this book.
Now, for your question--
It looks to me like breed2 is not an attribute of your object, it is instead a method.
use Class::Struct;
use strict;
use warnings;
struct Breed =>
{
name => '$',
cross => '$',
};
struct Cat =>
[
name => '$',
kittens => '#',
markings => '%',
breed => 'Breed',
];
my $cat = Cat->new( name => 'Socks',
kittens => ['Monica', 'Kenneth'],
markings => { socks=>1, blaze=>"white" },
breed => { name=>'short-hair', cross=>1 },
);
# Delegate to Breed::name
sub Cat::breed2 {
my $self = shift;
my $breed = $self->breed; # Get the breed object
my $name;
eval { $name = $breed->name(#_) };
warn "No breed specified for ".( $self->name )."\n"
unless defined $name;
return $name;
}
print "Once a cat called ", $cat->name, "\n",
"(which was a ", $cat->breed2, ")\n",
"had two kittens: ", join(' and ', #{$cat->kittens}), "\n";
Things get a bit hairier if you want to keep a set of pre-defined breeds, and have breed2 select a breed object by name if no value is set.
This stripped down Cat implementation uses class data to keep track of allowed cat breeds, and
package Cat;
use strict;
use warnings;
use Carp qw( croak );
my %breeds = map { $_->{name}, Breed->new( %$_ ) } (
{ name=>'short-hair', cross=>1 },
{ name=>'long-hair', cross=>1 },
{ name=>'siamese', cross=>0 },
);
sub new {
my $class = shift;
my %args = #_;
my $self = {};
bless $self, $class;
for my $arg ( keys %args ) {
$self->$arg( $args{$arg} ) if $self->can($arg);
}
return $self;
}
sub breed {
my $self = shift;
if( #_ ) {
my $v = shift;
croak "Illegal cat breed" unless eval {$v->isa( 'Breed' ) };
$self->{breed} = $v;
}
return $self->{breed};
}
sub breed2 {
my $self = shift;
my #breed_args;
if( #_ ) {
my $v = shift;
croak "$v is not a supported breed\n"
unless exists $breeds{$v};
#breed_args = ( $breeds{$v} );
}
my $breed = $self->breed(#breed_args);
return unless $breed;
return $breed->name;
}
Now, lets look at a Moose solution that uses all sorts of advanced goodies like type coercion and overloading:
BEGIN {
package Breed;
use Moose;
has 'name' => ( isa => 'Str', is => 'ro', required => 1 );
has 'cross' => ( isa => 'Bool', is => 'ro', required => 1 );
use overload '""' => \&_overload_string;
sub _overload_string {
my $self = shift;
return $self->name;
}
__PACKAGE__->meta->make_immutable;
no Moose;
1;
}
BEGIN {
package Cat;
use Moose;
use Moose::Util::TypeConstraints;
use Carp;
subtype 'MyTypes::CatBreed' => as class_type('Breed');
coerce 'MyTypes::CatBreed' =>
from 'Str'
=> via { Cat->supported_breed_by_name( $_ ) };
has 'name' => ( isa => 'Str', is => 'rw', required => 1 );
has 'kittens' => (
traits => ['Array'],
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub{ [] },
handles => {
all_kittens => 'elements',
add_kittens => 'push',
get_kitten => 'get',
count_kittens => 'count',
has_kittens => 'count',
},
);
has 'markings' => (
traits => ['Hash'],
is => 'ro',
isa => 'HashRef[Str]',
default => sub{ {} },
handles => {
set_marking => 'set',
get_marking => 'get',
has_marking => 'exists',
all_markings => 'keys',
delete_marking => 'delete',
},
);
has 'breed' => (
isa => 'MyTypes::CatBreed',
is => 'ro',
coerce => 1,
);
my %breeds;
sub supported_breed_by_name {
my $class = shift;
my $name = shift;
croak 'No breed name specified'
unless defined $name and length $name;
return $breeds{$name};
}
sub add_breed {
my $class = shift;
my $breed = shift;
croak 'No breed specified'
unless eval { $breed->isa('Breed') };
croak 'Breed already exists'
if exists $breeds{$breed};
$breeds{$breed} = $breed;
return $class;
}
sub delete_breed {
my $class = shift;
my $name = shift;
croak 'No breed name specified'
unless defined $name and length $name;
return delete $breeds{$name};
}
__PACKAGE__->meta->make_immutable;
no Moose;
1;
}
# Set up the supported breeds
Cat->add_breed($_) for map Breed->new( %$_ ), (
{ name=>'short-hair', cross=>1 },
{ name=>'long-hair', cross=>1 },
{ name=>'siamese', cross=>0 },
);
# Make a cat
my $cat = Cat->new( name => 'Socks',
kittens => ['Monica', 'Kenneth'],
markings => { socks=>1, blaze=>"white" },
breed => 'short-hair',
);
print
"Once a cat called ", $cat->name, "\n",
"(which was a ", $cat->breed, ")\n",
"had ", , " kittens: ", join(' and ', #{$cat->kittens}), "\n";
Don't use Class::Struct use Moose.
package Breed;
use Moose;
has 'name' => ( isa => 'Str', is => 'ro', required => 1 );
has 'cross' => ( isa => 'Bool', is => 'ro' );
package Cat;
use Moose;
has 'name' => ( isa => 'Str', is => 'ro', required => 1 );
has 'kittens' => ( isa => 'ArrayRef[Cat]', is => 'ro' );
has 'markings' => ( isa => 'HashRef', is => 'ro', default => sub { +{} } );
has 'breeds' => ( isa => 'ArrayRef[Breed]', is => 'ro' );
package main;
use Modern::Perl;
my $cat = Cat->new({
name => 'Socks',
, kittens => [ Cat->new({name=>'Monica'}), Cat->new({name=>'Kenneth'}) ]
, markings => { socks=>1, blaze=>"white" }
, breeds => [ Breed->new({ name=>'short-hair', cross => 1 }) ]
});
say "Once a cat called ", $cat->name;
say "Which was a:";
say "\t".$_->name for #{$cat->breeds};
say "had kittens:";
say "\t".$_->name for #{$cat->kittens};
In this scheme, a cat can have any number of Breeds, and a Cat can have any number of kittens which are also objects of Cat.
update to solve your problem specifically
You can make it implicit in the constructor the second breed is the first if it isn't supplied.
package Cat;
sub BUILD {
my $self = shift;
$self->breeds->[1] = $self->breeds->[0]
if $self->breeds->[0] && ! $self->breeds->[1]
}
You can pass in a token that identifies it as such, in the constructor (this should be easy but I can add an example if you want)
You can make Cat understand that if there is only one breed then both of the parents are the same
package Cat;
sub is_pure_bred { length #{$_[0]->breeds} == 1 ? 1 : 0 }
You can make ignore the breed of the cat, by setting it to undef, and determine the breed by that of the parents. This is because your breed is always a function of your lineage anyway. You can constraint this in a Moose trigger, the cat either requires two cat parents, or it requires a breed.
footnote Moose objects serialize fairly nice with XXX too:
... use XXX; YYY $cat;
--- !!perl/hash:Cat
breeds:
- !!perl/hash:Breed
cross: 1
name: short-hair
kittens:
- !!perl/hash:Cat
markings: {}
name: Monica
- !!perl/hash:Cat
markings: {}
name: Kenneth
markings:
blaze: white
socks: 1
name: Socks
...
You can fix this in a few ways, here are two of them:
use warnings;
use strict;
sub say {print #_, "\n"}
use Class::Struct;
struct Breed =>
{
name => '$',
cross => '$',
};
struct Cat =>
[
name => '$',
kittens => '#',
markings => '%',
breed => 'Breed',
breed2 => '$',
];
sub Cat::breed_name {shift->breed->name} #create a new accessor method
my $cat; # or declare $cat first
$cat = Cat->new( name => 'Socks',
kittens => ['Monica', 'Kenneth'],
markings => { socks=>1, blaze=>"white" },
breed => { name=>'short-hair', cross=>1 },
breed2 => sub { return $cat->breed->name;},
# this is now ok, but a bit awkward to call
);
print "Once a cat called ", $cat->name, "\n";
print "(which was a ", $cat->breed2->(), ")\n"; #returns then calls code ref
print "(which was a ", $cat->breed_name, ")\n"; #new accessor method
print "had two kittens: ", join(' and ', #{$cat->kittens}), "\n";
The reason your closure did not work right is because you can not close over a variable that is defined in the current statement. When the sub {...} tried to close around $cat it couldn't because it was not in scope yet. The solution is simply to predeclare the variable.
However, it doesn't seem like Class::Struct lets you install methods that way cleanly. Instead, adding a new accessor method to the Cat:: package lets you call the method as you would expect.