Suppose I want a class like this:
package Restraint;
use Moose;
has ball => (
is => 'ro',
does => 'Heavy',
);
has chain => (
is => 'ro',
does => 'Lockable',
);
has ball_class => (
is => 'ro',
isa => 'Str',
);
has chain_class => (
is => 'ro',
isa => 'Str',
);
1;
Now I want to instantiate the class like:
my $r = Restraint->new(
ball_class = 'Ball',
chain_class = 'Chain',
);
both arguments being mandatory.
I also want to instantiate both ball_class and chain_class during construction and assign them to corresponding attributes so that finally I can e.g. $r->ball->lift or $r->chain->lock etc.
How should I do that?
Just mark them both "required" - see Moose::Manual::Attributes
That's not going to do anything for "ball" or "chain" though. There's no connection.
You could set up ball/chain with lazy builders which would reference your classnames (see the Moose Manual again).
I'd probably just make the objects required and pass them in directly though:
my $r = Restraint->new(
ball => CannonBall->new(...),
chain => IronChain->new(...)
);
OK - you don't want to pass in objects. In that case you want lazy builders. Mark the ball as lazy and give it a builder and Moose will call the builder when the ball is first used.
http://metacpan.org/pod/Moose
http://metacpan.org/pod/Moose::Manual::Attributes
http://metacpan.org/pod/Moose::Cookbook::Basics::Recipe3
Do take the time to read the moose documentation - there's a lot of it, but it's quite well written and covers plenty of features. Lots of examples.
package Restraint;
use Moose;
has ball => (
is => 'ro',
does => 'Heavy',
lazy => 1,
builder => '_build_ball'
);
has ball_class => (
is => 'ro',
required => 1
);
sub _build_ball {
my $self = shift;
my $ball_class = $self->ball_class;
return $ball_class->new();
}
Using BUILD for instantiation while setting required only for *_class attributes seems to work to some extent, except that:
I had to make ball and chain read-write
Code:
package Restraint;
use Moose;
has ball => (
is => 'rw',
does => 'Heavy',
);
has chain => (
is => 'rw',
does => 'Lockable',
);
has ball_class => (
is => 'ro',
isa => 'Str',
required => 1,
);
has chain_class => (
is => 'ro',
isa => 'Str',
required => 1,
);
sub BUILD {
my $self = shift;
my $ball = $self->ball_class->new();
$self->ball( $ball );
my $chain = $self->chain_class->new();
$self->chain( $chain );
}
1;
It's not a big sacrifice for now, but I still wonder if there is more correct way, though.
Related
I have a Moose class with some properties (x,y,z). I subclass it, and for the subclass, x is always 3. How can I specify this in subclass?
One could use BUILDARGS.
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
return $class->$orig(#_, x => 3 );
};
I used to work with Moo but it seems to be the same. You just need to declare the property in the subclass using + to override previous declaration.
package Foo;
use Moose;
has 'a' => (
is => 'rw',
isa => 'Num',
);
has 'b' => (
is => 'rw',
isa => 'Num',
);
has 'c' => (
is => 'rw',
isa => 'Num',
);
package My::Foo;
use Moose;
extends 'Foo';
has '+a' => (
default => 3,
);
I'm trying to write a perl module using Mouse, and after the object has been initialized, but before the user makes any calls, I need to initialize object1 with the two values from object2 and object3 that the user was required to give. I tried to use Mouse's after feature to have a subroutine called after new:
package Test;
use strict;
use Mouse;
has 'object1' => ( is => 'rw', isa => 'Any');
has 'object2' => ( is => 'ro', isa => 'Str', required => 1);
has 'object3' => ( is => 'ro', isa => 'Str', required => 1);
after 'new' => sub {
my ($self) = #_;
$self->object1(#do stuff with object2 and object3);
};
1;
However, currently I get this error:
Invalid object instance: 'Test' at lib/Test.pm line 18.
Is there a way I can initialize a value with user supplied values before the user gets the object reference returned to them?
Mouse is compatible with Moose. Object creation has the following phases:
Arguments are passed through the BUILDARGS method if one is defined. This can munge the arguments before Moose/Mouse touch them, e.g. to provide default arguments or to accommodate other calling styles than the keyword convention.
A new instance is created and the fields (declared by has) are populated.
The object is now set up and ready to use (from Mouse's/Moose's perspective). You might have a different idea of a correct setup, and can perform your checks in a BUILD method. This is the point where you can also perform remaining initialization that can't be expressed with has declarations.
So your example might become:
use strict;
use warnings;
package Your::Class;
use Mouse;
has 'object1' => ( is => 'rw', isa => 'Any');
has 'object2' => ( is => 'ro', isa => 'Str', required => 1);
has 'object3' => ( is => 'ro', isa => 'Str', required => 1);
sub BUILD {
my ($self) = #_;
$self->object1($self->object2 . $self->object3);
};
package main;
use Test::More;
# Your::Class->new(object2 => "foo", object3 => "bar");
my $instance = new_ok('Your::Class', [object2 => "foo", object3 => "bar"]);
is($instance->object1, "foobar");
done_testing;
To learn more about object construction in Moose and Moose-compatible object systems, read Moose::Manual::Construction.
Is there some way in Moose to specify that I want an attribute to have a specific type, but also allow there to be a null value (undef?).
For example I am writing a simple implementation of a Linked List and have a Node class where the next and prev pointers are required to be of type Node (this is probably what you would expect)
package Node;
{
use Moose;
has 'value' => (
is => 'rw',
isa => 'Any', # Nodes can be of any type
);
has 'prev' => (
is => 'rw',
isa => 'Node',
predicate => 'has_prev',
);
has 'next' => (
is => 'rw',
isa => 'Node',
predicate => 'has_next',
);
}
But I was hoping to use a sentinel, empty node stored at the head of the list to mark the head, instead of an actual element of the list. So a list of elements [1, 2, 3] would actually look like:
EMPTY -> 1 -> 2 -> 3
I was hoping to be able to specify a empty value (like undef) for the next and prev pointers, but when I create an empty Node in my List class:
package List;
{
use Moose;
has 'head' => (
is => 'rw',
isa => 'Node',
# empty head node
default => sub {
Node->new( value => undef, next => undef, prev => undef );
},
);
Moose complains because undef is not of type Node.
Is there a way around this ?
You can use the Maybe[type] syntax to allow the type or undef. For your example:
has 'head' => (
is => 'rw',
isa => 'Maybe[Node]',
# empty head node
default => sub {
Node->new( value => undef, next => undef, prev => undef );
}
);
The next:
use 5.014;
use warnings;
package Node {
use Moose;
has 'value' => ( is => 'rw');
has 'prev' => ( is => 'rw', isa => 'Undef|Node', predicate => 'has_prev', default=>undef );
has 'next' => ( is => 'rw', isa => 'Undef|Node', predicate => 'has_next', default=>undef );
}
package List {
use Moose;
has 'head' => ( is => 'rw', isa => 'Node', default => sub { Node->new() } );
}
package main;
use Data::Dumper;
my $list = List->new();
say Dumper $list;
prints:
$VAR1 = bless( {
'head' => bless( {
'next' => undef,
'prev' => undef
}, 'Node' )
}, 'List' );
The Moose::Manual::Types says for the basic hier:
Undef <---- undefined
Defined
Value
Str
Num
Int
ClassName <---- Class name
RoleName
and later in the section TYPE UNIONS says:
Moose allows you to say that an attribute can be of two or more
disparate types. For example, we might allow an Object or FileHandle:
has 'output' => (
is => 'rw',
isa => 'Object | FileHandle', );
As others already says, here is a Maybe[Something] too, I haven't idea what is better, but the Something | SomethingOther looks more "perlish" (IMHO). ;)
The authors prefer Undef|Node over Maybe[Node].
has 'prev' => (
is => 'rw',
isa => 'Undef|Node',
predicate => 'has_prev',
);
With Moose, you can have lazy builders on attributes, where the builder is called when the attribute is first accessed if the attribute was not already populated. You can have type coercion of an attribute with coerce, but this is applied whenever the attribute is set, so even on object initialization.
I'm looking for a way to implement lazy coercion, where an attribute may be initially populated, but is only coerced when it is first accessed. This is important when coercion is expensive.
In the following example, I use a union type and method modifiers to do this:
package My::Foo;
use Moose;
has x => (
is => 'rw',
isa => 'ArrayRef | Int',
required => 1
);
around "x" => sub {
my $orig = shift;
my $self = shift;
my $val = $self->$orig(#_);
unless(ref($val)) {
# Do the cocerion
$val = [ map { 1 } 1..$val ];
sleep(1); # in my case this is expensive
}
return $val;
};
1;
my $foo = My::Foo->new( x => 4 );
is_deeply $foo->x, [ 1, 1, 1, 1 ], "x converted from int to array at call time";
However there are a few problems with this:
I dislike the union type + method modifier approach. It goes against the "Best Practices" suggestion to use coercion instead of unions. It isn't declarative.
I need to do this with many attributes across many classes. Therefore some form of DRY is needed. This could be meta-attribute roles, type-coercion, what have you.
Update:
I followed ikegami's suggestion to encapsulate the expensive type coercion inside an object and provide an outer coercion to this object:
package My::ArrayFromInt;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'My::ArrayFromInt::Inner',
as 'ArrayRef[Int]';
coerce 'My::ArrayFromInt::Inner',
from 'Int',
via { return [ (1) x $_ ] };
has uncoerced => (is => 'rw', isa => 'Any', required => 1);
has value => (
is => 'rw',
isa => 'My::ArrayFromInt::Inner',
builder => '_buildValue',
lazy => 1,
coerce => 1
);
sub _buildValue {
my ($self) = #_;
return $self->uncoerced;
}
1;
package My::Foo;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'My::ArrayFromInt::Lazy' => as class_type('My::ArrayFromInt');
coerce 'My::ArrayFromInt::Lazy',
from 'Int',
via { My::ArrayFromInt->new( uncoerced => $_ ) };
has x => (
is => 'rw',
isa => 'My::ArrayFromInt::Lazy',
required => 1,
coerce => 1
);
1;
This works if $foo->x->value is called. However this doesn't solve point #2, as I would need to create My::ArrayFromInt and the ::Lazy subtype for each attribute I would like to transform. And I'd like to avoid calling $foo->x->value if possible.
How about having the typedef along the lines described, then doing
has _x => (
is => 'ro',
isa => 'Int|MyArrayOfInts',
init_arg => 'x',
required => 1,
);
has x => (
is => 'ro',
lazy => 1,
isa => 'MyArrayOfInts',
coerce => 1,
default => sub { $_[0]->_x },
);
It'd make sense to wrap that up into some kind of helper method to create the pair of objects along the lines of
has_lazily_coerced x => (
is => 'ro',
isa => 'TargetType',
);
which would introspect on TargetType to get a list of legal types for the uncoerced shadow attribute and generate the pair of attributes for you.
package testDB;
use Moose;
use Carp;
use SQL::Library;
has 'lib' => (#FOLDBEG
is => 'rw',
isa => 'Str',
default => 'default',
trigger => \&_sql_lib_builder,
);#FOLDEND
has 'lib_dir' => (#FOLDBEG
is => 'ro',
isa => 'Str',
default => '/SQL',
);#FOLDEND
has '_sql_lib' => (#FOLDBEG
builder => '_sql_lib_builder',
is => 'rw',
isa => 'Str',
);
has '_sql_lib' => (#FOLDBEG
builder => '_sql_lib_builder',
is => 'rw',
handles => {
get_sql => 'retr',
get_elements => 'elements',
},
);
sub _sql_lib_builder {
my ($self, $lib) = shift();
$self->lib() or die("I am unable to get a lib.");
$lib = $self->lib unless $lib;
my $lib_dir = $self->lib_dir;
print $lib_dir."\n";
my $lib_file = $lib_dir . '/' . $lib . '.sql';
unless (-e $lib_file ) {
confess "SQL library $lib does not exist";
}
my $library = new SQL::Library { lib => $lib_file };
$self->_sql_lib($library);
}#FOLDEND
__PACKAGE__->meta->make_immutable;
my $tdb=testDB->new();
Using perl 5.8.8 and Moose 2.0205
$ perl testDB.pl
I am unable to get a lib. at testDB.pl line 35.
You've defined the _sql_lib attribute twice, once saying isa Str and once saying it handles methods (which a Str doesn't), but that's not the problem you're talking about.
The main problem is that you didn't define _sql_lib with lazy => 1. Any attribute whose builder (or default) depends on other attributes of the object must be lazy, because Moose doesn't guarantee the order in which attributes are assigned values during object construction.
# REMOVE THIS:
#has '_sql_lib' => (#FOLDBEG
# builder => '_sql_lib_builder',
# is => 'rw',
# isa => 'Str',
#);
has '_sql_lib' => (#FOLDBEG
builder => '_sql_lib_builder',
is => 'rw',
lazy => 1, # ADD THIS LINE
handles => {
get_sql => 'retr',
get_elements => 'elements',
},
);
The reason that make_immutable brings out the bug is that calling it generates a different constructor for your class, which happens to initialize the attributes in a different order.