Say that I have two roles: Simple::Tax and Real::Tax. In testing situations, I want to use Simple::Tax, and in production, I want to use Real::Tax. What is the best way to do this? My first thought was to use different versions of the new method to create objects with different roles:
#!/usr/bin/perl
use warnings;
{
package Simple::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
return int($self->price * 0.05);
}
}
{
package A;
use Moose;
use Moose::Util qw( apply_all_roles );
has price => ( is => "rw", isa => 'Int' ); #price in pennies
sub new_with_simple_tax {
my $class = shift;
my $obj = $class->new(#_);
apply_all_roles( $obj, "Simple::Tax" );
}
}
my $o = A->new_with_simple_tax(price => 100);
print $o->calculate_tax, " cents\n";
My second thought was to use an if statement in the body of package to use different with statements:
#!/usr/bin/perl
use warnings;
{
package Complex::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
#pretend this is more complex
return int($self->price * 0.15);
}
}
{
package Simple::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
return int($self->price * 0.05);
}
}
{
package A;
use Moose;
has price => ( is => "rw", isa => 'Int' ); #price in pennies
if ($ENV{TEST_A}) {
with "Simple::Tax";
} else {
with "Complex::Tax";
}
}
my $o = A->new(price => 100);
print $o->calculate_tax, " cents\n";
Is one of these better than the other, is there something horrible about either of them, and is there a better way I haven't thought of yet.
My first suggestion would be something like MooseX::Traits and then specify the different roles at object creation:
my $test = A->with_traits('Simple::Tax')->new(...);
my $prod = A->with_traits('Complex::Tax')->new(...);
But this opens the door to an A being created without either Role being applied. So thinking about it further, I think you've got an X/Y problem. If Simple::Tax is only ever used to mock up Complex::Tax in a test environment you can do several things to override the Complex::Tax implementation.
For example you could just define Simple::Tax like so:
package Simple::Tax;
use Moose::Role;
requires 'calculate_tax';
around calculate_tax => sub { int($_[1]->price * 0.05) };
Then always have A compose Complex::Tax and apply Simple::Tax to it only during tests (using apply_all_roles).
If however you need Simple::Tax and Complex::Tax both in production (and not simply for testing) your best bet is refactor from a composition relationship (does) to a delegation relationship (has).
package TaxCalculator::API;
use Moose::Role;
requires qw(calculate_tax);
package SimpleTax::Calculator;
use Moose;
with qw(TaxCalculator::API);
sub calculate_tax { ... }
package ComplexTax::Calculator;
use Moose;
with qw(TaxCalculator::API);
sub calcuate_tax { ... }
package A;
use Moose;
has tax_calculator => (
does => 'TaxCalculator::API',
handles => 'TaxCalculator::API',
default => sub { ComplexTax::Calculator->new() },
);
Then if you want to override it you simply pass in a new tax_calculator:
my $test = A->new(tax_calculator => SimpleTax::Calculator->new());
my $prod = A->new(tax_calculator => ComplexTax::Calculator->new());
Because handles will delegate all of the methods from the role as new proxies this is practically identical to having composed the role yourself.
Related
I'm trying to create the abstract method pattern using Perl and Moose. What I don't understand is that if I override a method from the AbstractClass it will eventually be called anyway. Why is this and is there a way to avoid the superclass from being called?
Main
package main;
use AbstractSort;
use OrderedSort;
# Sub class test
my $ordered = OrderedSort->new(array => [1, -1, 23, 34123, -24324]);
$ordered->sortData();
AbstractClass
package AbstractSort;
use namespace::autoclean; # Trims EXPORTER
use Moose;
has 'array' => (traits => ['Array'],
is => 'ro',
isa => 'ArrayRef[Int]',
default => sub { [] },
handles => {
get_array => 'get',
count_array => 'count',
});
sub sortData{
my $self = shift;
print "Sorting data..\n";
_sortAlgorithm($self->array);
# ...
}
# Protected method here is the actual algorithm
sub _sortAlgorithm {
die 'You must override _sortAlgorithm() in a subclass';
# but Moose will always call the superclass which then makes it die
}
SubClass
package OrderedSort;
use namespace::autoclean; # Trims EXPORTER
use Moose;
extends 'AbstractSort';
# Override and mmpl _sortAlgorithm
override _sortAlgorithm => sub {
my $self = shift;
# ....
};
before '_sortAlgorithm' => sub {
my $self = shift;
# ...
return;
};
You are calling _sortAlgorithm as a function in the same package in AbstractSort`, and not as a method.
sub sortData {
my $self = shift;
# there is something missing here!
_sortAlgorithm( $self->array );
}
That way, it will always be called in the same package, because it's not an OOP method call.
You need to do $self->_sortAlgorithm instead.
sub sortData {
my $self = shift;
print "Sorting data..\n";
$self->_sortAlgorithm( $self->array );
# ...
}
It will now not die any more, because it looks up the _sortAlgorithm method on $self, which is an instance of your subclass.
The fact that you actually have my $self = shift on your overridden method could have given that away, as you were also not passing $self into it.
You should also not be passing around $self->array. The algorithm method also has access to $self->array, so if you want to sort the data that is attached to your object, just use it directly in there.
Also note that typical naming conventions in Perl suggest snake_case method and variable names, and CamelCase package names.
I want to be able to have a Moo* class with these characteristics:
an object's attribute can store a reference to the object itself
that attribute will be type-constrained using a Type::Tiny type so the reference must be of the right type
the class must function when it is immutable, and the attribute is "required", i.e. an undefined value is unacceptable and it cannot be updated later
E.g.
package GraphQLType;
use Moo;
use Types::Standard -all;
has [qw(children)] => (
is => 'rwp',
isa => ArrayRef[InstanceOf['GraphQLType']],
required => 1,
);
package main;
my $type;
$type = GraphQLType->new(children => [$type]);
The above presents a chicken-and-egg problem: $type will be undefined and therefore fail the type constraint.
A pattern used in graphql-js is "thunking". In Perl terms:
package GraphQLType;
use Moo;
use Types::Standard -all;
has [qw(children)] => (
is => 'rwp',
isa => CodeRef | ArrayRef[InstanceOf['GraphQLType']],
required => 1,
);
package main;
my $type;
$type = GraphQLType->new(children => sub { [$type] });
While that works for the specific type there, how can I have a parameterised type that implements something like this? Also, it will help even more if this can hook into the "lazy" functionality to minimise the code involved in storing the computed value.
package Thunking;
use Moo;
use Types::Thunking -all;
use Types::Standard -all;
has [qw(children)] => (
is => 'lazy',
isa => Thunk[ArrayRef[InstanceOf['GraphQLType']]],
required => 1,
);
Two issues need to be dealt with here: a parameterised Type::Tiny type constraint for a delayed-computation immutable attribute (DCIA), and an actually-functioning DCIA.
Parameterised type
Since this is Perl, there is more than one way to do this. The heart of making a parameterised type in Type::Tiny is to provide a constraint_generator parameter. The most idiomatic way to do this, using only Type::Tiny components, is:
package Types::Thunking;
use Types::TypeTiny -all;
use Type::Library -base;
use Type::Utils -all;
declare "Thunk", constraint_generator => sub { union [ CodeLike, #_ ] };
That's it! If no parameters are given, it works just like a CodeLike. The libraries can take care of any "inline" code generating.
The reason it can be so short is that the constraint_generator must return either a code-ref, which would probably be a closure that captures the parameters passed to it (see below), or simply a Type::Tiny - in which case the other parameterisability parameters are not needed. Since union (which looks like it's normally intended for producing arguments to a declare) returns a suitably-constructed Type::Tiny::Union, it just drops in perfectly.
A more spelled-out version, not using a union type (and for brevity, using CodeRef not CodeLike:
package Types::Thunking;
use Types::Standard -all;
use Type::Library -base;
use Type::Utils -all;
declare "Thunk",
constraint_generator => sub {
my ($param) = #_;
die "parameter must be a type" if grep !UNIVERSAL::isa($_, 'Type::Tiny'), #_;
return sub { is_CodeRef($_) or $param->check($_) };
},
inline_generator => sub {
my ($param) = #_;
die "parameter must be a type" if grep !UNIVERSAL::isa($_, 'Type::Tiny'), #_;
return sub {
my ($constraint, $varname) = #_;
return sprintf(
'Types::Standard::is_CodeRef(%s) or %s',
$varname,
$param->inline_check($varname),
);
};
};
This is the "harness" I used for testing these:
#!/usr/bin/perl
use Thunking;
sub do_test {
use Data::Dumper; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 0;
my ($args, $should_work) = #_;
my $l = eval { Thunking->new(#$args) };
if (!$l) {
say "correctly did not work" and return if !$should_work;
say "INcorrectly did not work" and return if $should_work;
}
my $val = eval { $l->attr };
if (!$val) {
say "correctly did not work" and return if !$should_work;
say "INcorrectly did not work" and return if $should_work;
}
say(($should_work ? "" : "INcorrectly worked: "), Dumper $val);
}
do_test [attr => { k => "wrong type" }], 0;
do_test [attr => ["real value at init"]], 1;
do_test [attr => sub { [ "delayed" ] }], 1;
do_test [attr => sub { { k => "delayed wrong type" } }], 0;
Delayed-computation immutable attribute
In order to make this immutable, we want setting the attribute to fail unless it's us doing it. When reading the attribute, we want to see whether there is computation to be done; if yes, do it; then return the value.
Naive approach
package Thunking;
use Moo;
use Types::Standard -all;
use Types::Thunking -all;
has attr => (
is => 'rwp',
isa => Thunk[ArrayRef],
required => 1,
);
before 'attr' => sub {
my $self = shift;
return if #_; # attempt at setting, hand to auto
my $value = $self->{attr};
return if ref($value) ne 'CODE'; # attempt at reading and already resolved
$self->_set_attr($value->());
}
The before should be fairly self-explanatory but you will see it manually looks in the object's hash-ref, which is usually a clue that your programming is not finished yet. Also, it's rwp and requires the before in the class, which is far from pretty.
Using MooX modules
An approach that tries to generalise this with a separate module, MooX::Thunking. First, another module to encapsulate overriding of Moo functions:
package MooX::Utils;
use strict;
use warnings;
use Moo ();
use Moo::Role ();
use Carp qw(croak);
use base qw(Exporter);
our #EXPORT = qw(override_function);
sub override_function {
my ($target, $name, $func) = #_;
my $orig = $target->can($name) or croak "Override '$target\::$name': not found";
my $install_tracked = Moo::Role->is_role($target) ? \&Moo::Role::_install_tracked : \&Moo::_install_tracked;
$install_tracked->($target, $name, sub { $func->($orig, #_) });
}
Now the thunking MooX module itself, which uses the above to override has:
package MooX::Thunking;
use MooX::Utils;
use Types::TypeTiny -all;
use Class::Method::Modifiers qw(install_modifier);
sub import {
my $target = scalar caller;
override_function($target, 'has', sub {
my ($orig, $name, %opts) = #_;
$orig->($name, %opts), return if $opts{is} ne 'thunked';
$opts{is} = 'ro';
$orig->($name, %opts); # so we have method to modify
install_modifier $target, 'before', $name => sub {
my $self = shift;
return if #_; # attempt at setting, hand to auto
my $value = $self->{$name};
return if !eval { CodeLike->($value); 1 }; # attempt at reading and already resolved
$self->{$name} = $value->();
$opts{isa}->($self->{$name}) if $opts{isa}; # validate
}
});
}
This applies "thunking" to an attribute. It will only function if the attribute is ro, and will quietly resolve any CodeLike values on reading. It can be used like this:
package Thunking;
use Moo;
use MooX::Thunking;
use Types::Standard -all;
use Types::Thunking -all;
has attr => (
is => 'thunked',
isa => Thunk[ArrayRef],
);
Using BUILDARGS and lazy
An alternative approach, suggested by the mighty #haarg:
package MooX::Thunking;
use MooX::Utils;
use Types::TypeTiny -all;
use Class::Method::Modifiers qw(install_modifier);
sub import {
my $target = scalar caller;
override_function($target, 'has', sub {
my ($orig, $name, %opts) = #_;
$orig->($name, %opts), return if $opts{is} ne 'thunked';
$opts{is} = 'lazy';
my $gen_attr = "_gen_$name";
$orig->($gen_attr => (is => 'ro'));
$opts{builder} = sub { $_[0]->$gen_attr->(); };
install_modifier $target, 'around', 'BUILDARGS' => sub {
my ($orig, $self) = (shift, shift);
my $args = $self->$orig(#_);
$args->{$gen_attr} = delete $args->{$name} if eval { CodeLike->($args->{$name}); 1 };
return $args;
};
$orig->($name, %opts);
});
}
It uses the built-in lazy mechanism, creating a builder that will call the supplied CodeLike if that is what is given. One important downside is that this technique does not work for Moo::Roles.
I'd like to check on every call to my object's methods some value (in this case: token's age). Is it possible to set it to all methods at once? Like in constructor? I have such simple constructor:
sub new {
my $class = shift;
my %args = #_;
my $self = {};
$self->{key} = $args{key};
bless($self, $class);
($self->{token}, $self->{token_start}) = $self->_get_authorized_token();
return $self;
}
And bunch of methods, which depends of tokens age, like this:
sub add_item {
my $self = shift;
my %args = #_;
...
}
I'd like to avoid including age-checking in every method, so i look for more general way to implement it. Has there some?
All I can think of is to hide all your 'real' methods - either in the classical way with a preceding underscore, or in a hash of subroutines - and use AUTOLOAD to direct the call properly.
The example below shos the idea
module MyClass.pm
package MyClass;
use strict;
use warnings;
sub new {
bless {}, __PACKAGE__;
}
sub _method1 {
print "In method1\n";
}
sub _method2 {
print "In method2\n";
}
sub AUTOLOAD {
our $AUTOLOAD;
my ($class, $method) = $AUTOLOAD =~ /(.+)::(.+)/;
return if $method eq 'DESTROY';
my $newmethod = "${class}::_$method";
unless (exists &$newmethod) {
die qq(Can't locate object method "$method" via package "$class");
}
print "Preprocessing...\n";
goto &$newmethod
}
1;
program
use strict;
use warnings;
use MyClass;
my $thing = MyClass->new;
$thing->method1;
$thing->method2;
$thing->method3;
output
Preprocessing...
In method1
Preprocessing...
In method2
Can't locate object method "method3" via package "MyClass" at MyClass.pm line 23.
See Class::Method::Modifiers or Class::Method::Modifiers::Fast module.
I honestly think that if you're doing OO in Perl and you want to deal with things like attributes, method modifiers and deferred resource loading without the boilerplate, it's worth investing in learning Moose. To illustrate, this is one way to write what you want using Moose:
use Moose;
has key => (isa => 'Str', is => 'ro');
has token => (isa => 'HashRef', is => 'ro', lazy_build => 1);
before [qw(add_item method2 method3)] => sub {
my $self = shift;
if (do something with $self->token) {
# return, die, etc.
}
};
sub _build_token {
my $self = shift;
my $key = $self->key;
return { token => 'foo', token_start => time };
}
These might be helpful:
Moose::Manual::MethodModifiers
Moose::Cookbook::Basics::BinaryTree_BuilderAndLazyBuild
While refactoring I'm trying to retain some backwards compatibility for a time. I'm wondering if it's possible to have a method on an object, but prevent that method from being inherited by classes that subclass it? e.g. given
package Class {
use Moose;
sub foo { 'test' };
}
my $class = Class->new;
$class->foo;
would work, but
package Extended::Class {
use Moose;
extends 'Class';
}
my $class = Extended::Class->new;
$class->foo;
would not.
I realize this probably breaks some principle or another, but I'm deprecating these interfaces as I go.
How about:
use 5.014;
package Class {
use Carp qw( croak );
use Moose;
sub foo {
my $self = shift;
croak unless __PACKAGE__ eq ref $self;
return 'test';
}
}
package Extended::Class {
use Moose;
extends 'Class';
}
package main {
my $x = Class->new;
say $x->foo;
my $y = Extended::Class->new;
say $y->foo;
}
Have you considered delegation?
package Original {
use Moose;
sub foo { 23 }
sub bar { 42 }
}
package Subclass {
use Moose;
has original => (
buidler => '_build_original',
handles => [qw( bar )],
);
sub _build_original { Original->new }
}
Of course it depends on your situation if you can use it. The subclass won't pass isa checks for the above (but you can override isa if you must). Also passing the original arguments on to the object you're extending can be annoying depending on the use case.
Since it would look for the method foo in the Extended::Class first, you could just declare one there that doesn't do anything. That way the inherited one would not be called unless you do so somewhere in your subclass.
I'm not sure if Moose alters that behaviour, though.
package Class {
use Moose;
sub foo { 'test' }
}
package Extended::Class {
use Moose;
extends 'Class';
sub foo {
# do nothing
}
}
package main {
my $x = Class->new;
my $y = Extended::Class->new;
print $x->foo;
print $y->foo;
}
OK. I have a problem trying to inherit constants set in a parent class for any of the child classes.
#!/usr/bin/perl
use strict;
use warnings;
package Car;
use Exporter qw( import );
our #EXPORT_OK = ( 'WHEELS', 'WINGS' );
use constant WHEELS => 4;
use constant WINGS => 0;
sub new {
my ( $class, %args ) = #_;
my $self = {
doors => $args{doors},
colour => $args{colour},
wheels => WHEELS,
wings => WINGS,
};
bless $self, $class;
return $self;
}
package Car::Sports;
use base qw( Car );
sub new {
my ( $class, %args ) = #_;
my $self = {
doors => $args{doors},
engine => $args{engine},
wheels => WHEELS,
wings => WINGS,
};
bless $self, $class;
return $self;
}
package main;
my $obj = Car->new( doors => 4, colour => "red" );
print Dumper $obj;
my $obj2 = Car::Sports->new( doors => 5, engine => "V8" );
print Dumper $obj2;
__END__
The error is:
Bareword "WHEELS" not allowed while "strict subs" in use at ./t.pl line 30.
Bareword "WINGS" not allowed while "strict subs" in use at ./t.pl line 30.
Execution of ./t.pl aborted due to compilation errors.
Now, I haven't come here to post without doing some research. I understand that one option would be to use Car qw( WHEELS WINGS) in Car::Sports. However, if I do that I get the following error, because the classes are all inline in the same file:
Can't locate Car.pm in #INC
For a variety of reasons, I need to keep my packages in one file. Is there a way around this? As constants are basically just subs, why do I have to import them when the same would not be true for a normal method?
Finally, I also know I can do this:
package Car::Sports;
use base qw( Car );
sub new {
my ( $class, %args ) = #_;
my $self = {
doors => $args{doors},
engine => $args{engine},
wheels => Car::WHEELS,
wings => Car::WINGS,
};
bless $self, $class;
return $self;
}
And it's fine... But I have a number of classes and want to make the inheritance of constants more generic that having to name the parent class explicitly (and sometimes it's not just the parent class, but the grandparent).
Many thanks in advance for any pointers!
Cheers
One workaround is to include the line
package Car::Sports;
use base qw( Car );
Car->import(qw(WHEELS WINGS));
AND use the sigils in the Car::Sports constructor:
...
wheels => &WHEELS,
wings => &WINGS,
...
Your Car class isn't defining its #EXPORTS_OK list until run-time. The sigils are required because the Car::Sports constructor is parsed at compile-time, and the compiler doesn't know there should be WHEELS and WINGS symbols in the Car::Sports namespace.
The only way to avoid the sigils is to define Car's exports at compile-time:
package Car;
our #EXPORT_OK;
BEGIN {#EXPORT_OK = qw(WHEELS WINGS)} # set at compile not run time
...
package Car::Sports;
use base qw(Car);
BEGIN {Car->import('WHEELS','WINGS')} # import before c'tor is parsed
You could also avoid these machinations by defining the Car class in its own Car.pm file. Then you would just say
use Car qw(WHEELS WINGS);
and everything in the Car.pm file would be parsed at compile time, AND the Exporter::import method (triggered by a call to Car::import) would automatically get run and import the desired symbols to your current namespace.
May this change suit your needs?
[...]
wheels => $class->SUPER::WHEELS,
wings => $class->SUPER::WINGS,
[...]
Using Data::Dumper you get:
$VAR1 = bless( {
'wings' => 0,
'colour' => 'red',
'doors' => 4,
'wheels' => 4
}, 'Car' );
$VAR1 = bless( {
'wings' => 0,
'engine' => 'V8',
'doors' => 5,
'wheels' => 4
}, 'Car::Sports' );
Alternative, you could do exactly what use does:
BEGIN {
package Car;
use Exporter qw( import );
#EXPORT_OK = qw( WHEELS );
...
$INC{'Car.pm'} = 1;
}
BEGIN {
package Car::Sports;
use Car qw( WHEELS );
#ISA = 'Car';
...
$INC{'Car/Sports.pm'} = 1;
}
Generally, exposing that something is a constant to any package other than the one defining it is actually a bad idea. This argues, among other things, against using unusual forms when referring to values that happen to be constant in other areas of your code.
The constant module actually supports an invocation form that hides the fact that we're talking about constants, inasmuch as calling constants as class methods works just fine:
package Car;
use constant default_wheel_count => 4;
package Car::Sports;
sub new {
my ($class) = #_;
return bless {
wheels => $class->default_wheel_count,
} => $class;
}
That's how one actually inherits constants, but it's still probably the wrong approach. Eliminating the copypasta by only using the constants from the classes that implement construction of those attributes is the actual right thing to do.