> From: Jesse Luehrs <[email protected]>
>No, overriding is a perfectly valid way to resolve conflicts, and always
>has been. alias and excludes are only important to fix cases where this
>isn't possible, and this change should remove the last of those.
So I can avoid spamming the list regarding things already hashed out, can you
point me to the prior discussions where this change was decided? I'm hoping to
better understand other's thinking on this (I've discussed this with mst, but I
don't think it would be fair to assume that his opinions apply to everyone
else).
My concern about removing "alias" and "excludes" stems in part from how I
appear to have a slightly different philosophy about OO programming. I view
objects as experts about solving a given problem, but the underlying object is
composed of "legos" of roles. Need some new behavior? I rummage around in my
toy box looking for the legos to snap onto my existing object. Viewing each
object as a collection of well-defined behaviors rather than "how can I squeeze
this into a tree or graph hierarchy" lets me just get things done, though I
realize I may be in the minority here.
For example, in my first programming job, I worked several hours a week as a
programmer and the rest of the week I was a glorified clerk. I was (illegally)
paid at different rates for each job. So the "employee" sometimes behaves as a
clerk and sometimes behaves as a programmer. Calculating my pay with roles is
fairly straightforward (funky formatting to make it easier to fit in the email):
{ package R::Programmer; use Moose::Role; sub pay_rate {12} }
{ package R::Clerk; use Moose::Role; sub pay_rate {10} }
{ package R::Employee; use Moose;
with 'R::Programmer' => {
# this is why I want a "rename" property here
exclude => 'pay_rate',
alias => { pay_rate => 'programmer_pay_rate' }
},
'R::Clerk' => {
exclude => 'pay_rate',
alias => { pay_rate => 'clerk_pay_rate' }
};
has [qw/hours programming_hours drudgery/] => ( is => 'ro' );
sub pay_rate {8} # ugly hack for quick example. Oops
sub paycheck {
my $self = shift;
return $self->programmer_pay_rate * $self->programming_hours
+ $self->clerk_pay_rate * $self->drudgery;
}
}
my $employee = R::Employee->new(
programming_hours => 15,
drudgery => 25,
);
say $employee->paycheck;
(yes, there's some cruft in this quickly hacked together example and it's
deliberately kept simple (e.g., pay_rate) to focus on the role composition
issue)
How do I do that without roles? I'm not going to fall back on MI because that
introduces the very problems that roles were trying to solve! Thus, delegation
seems to be the trick (if I'm creating a straw man argument here, my apologies.
Please correct me). Here's the first pass at that:
{ package R::Paycheck; use Moose::Role;
requires 'pay_rate';
has [qw/hours_worked/] => ( is => 'ro' );
sub paycheck {
my $self = shift;
return $self->hours_worked * $self->pay_rate;
}
}
{ package C::Programmer; use Moose; with 'R::Paycheck'; sub pay_rate { 12 }
}
{ package C::Clerk; use Moose; with 'R::Paycheck'; sub pay_rate { 10 }
}
{ package C::Employee; use Moose;
has [qw/programming_hours drudgery/] => ( is => 'ro' );
has 'programmer' => ( is => 'ro', lazy => 1, builder =>
'_build_programmer' );
has 'clerk' => ( is => 'ro', lazy => 1, builder => '_build_clerk'
);
sub _build_programmer {
my $self = shift;
C::Programmer->new( hours_worked => $self->programming_hours );
}
sub _build_clerk {
my $self = shift;
C::Clerk->new( hours_worked => $self->drudgery );
}
sub paycheck {
my $self = shift;
return $self->programmer->paycheck + $self->clerk->paycheck;
}
}
In some respects it's cleaner, but in other respects, it's not. For example,
many of my roles are things that shouldn't necessarily be instantiated on their
own (Serializable), but if I'm forced to use delegation for composition because
I don't have exclusion and aliasing, then sometimes I'm going to have to fall
back on the old hack of creating classes to add some behavior, even if the
"class" shouldn't really be a class. (See also:
http://steve-yegge.blogspot.fr/2006/03/execution-in-kingdom-of-nouns.html).
Further, while I realize my "lego" approach to building classes isn't to
everyone's tastes, it does tend to shake out bugs in role implementations very
quickly. For example, I've had to give up on Moo because its roles are so buggy
(https://rt.cpan.org/Public/Bug/Display.html?id=82711). So unless there's a
really concrete, demonstrable problem with aliasing and excluding, I'm very
concerned about taking away these tools that have been in my toolbox for many
years.
Finally, we can consider performance. When a role's methods are flattened into
my class, they're going to be faster than delegation.
use Benchmark;
my %args = (
programming_hours => 15,
drudgery => 25,
);
timethese(
100000,
{ roles => sub { R::Employee->new(%args)->paycheck },
classes => sub { C::Employee->new(%args)->paycheck },
}
);
In this example, we see that roles are more than twice as fast as delegation:
Benchmark: timing 100000 iterations of classes, roles...
classes: 14 wallclock secs (14.20 usr + 0.00 sys = 14.20 CPU) @
7042.25/s (n=100000)
roles: 6 wallclock secs ( 5.63 usr + 0.00 sys = 5.63 CPU) @
17761.99/s (n=100000)
As it turns out, object creation is very expensive, so if we skip that part and
just call the salary method (and we'll bump this up to one million iterations):
use Benchmark;
my %args = (
programming_hours => 15,
drudgery => 25,
);
my $roles = R::Employee->new(%args);
my $classes = C::Employee->new(%args);
timethese(
1000000,
{ roles => sub { $roles->paycheck },
classes => sub { $classes->paycheck },
}
);
Benchmark: timing 1000000 iterations of classes, roles...
classes: 3 wallclock secs ( 2.88 usr + 0.00 sys = 2.88 CPU) @
347222.22/s (n=1000000)
roles: 0 wallclock secs ( 1.28 usr + 0.00 sys = 1.28 CPU) @
781250.00/s (n=1000000)
We still see the roles as slightly twice as fast as delegation.
In short:
1. Allowing a role to silently override a consumed role's methods moves Perl
roles even further away from Smalltalk-style traits and its associative and
commutative guarantees.
Regarding plans to remove 'alias' and 'excludes':
2. Intending to remove the aliasing and excluding composition tools removes
tools that allow finer control over how you consume your objects.
3. Removing certain use cases for role composition and forcing developers to
fall back on delegation is a significant performance hit.
Cheers,
Ovid--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/