Slide 1

Slide 1 text

(Parameterized) Roles Shawn M Moore Best Practical Solutions http://sartak.org Friday, September 11, 2009 Presented YAPC::Asia, 2009-09-11. Tokyo Institute of Technology, Tokyo, Japan.

Slide 2

Slide 2 text

"The more I use roles, the less I understand why anyone would want to use the inheritance model." - Ovid "A role-aware type system allows you to express yourself with better genericity." - chromatic "Roles are the sound of diamond inheritance that people have stopped banging their heads against." - hdp ࠷ۙ࿩୊ͷʮϩʔϧ(໾ׂ)ʯ ྲྀߦΔͷʹ͸ཧ༝͕͋Γ·͢ Friday, September 11, 2009 You've probably heard a lot about this new feature called "roles" lately. Not only are a lot of people talking about roles, but a lot of people are using roles. And for good reason!

Slide 3

Slide 3 text

Example Role package Counter; use Moose::Role; has counter => ( is => 'ro', isa => 'Int', default => 0, ); sub increment { my $self = shift; $self->counter($self->counter + 1); } ϩʔϧͷղઆΛ͢Δલʹɺ ·ͣ͸αϯϓϧΛ͓ݟͤ͠·͢ Friday, September 11, 2009 Before I get into what a role is, and why roles are awesome, I want to just dive into what a role looks like.

Slide 4

Slide 4 text

Example Role package Counter; use Moose::Role; has counter => ( is => 'ro', isa => 'Int', default => 0, ); sub increment { my $self = shift; $self->counter($self->counter + 1); } useจ͕ҟͳΔ͚ͩͰɺMooseͷ Ϋϥεʹͦͬ͘ΓͰ͢Ͷ Friday, September 11, 2009 You might notice a strong resemblance to regular Moose code. The only difference is that we "use Moose::Role" instead of "use Moose". This means that this package represents a role instead of a class. Oh, damnit, wait a second.

Slide 5

Slide 5 text

Example Role use MooseX::Declare; role Counter { has counter => ( is => 'ro', isa => 'Int', default => 0, ); method increment { $self->counter($self->counter + 1); } } ΋ͬͱ͖ͬ͢Γͤͯ͞Έ·ͨ͠ Friday, September 11, 2009 Much better. Anyway, so we have a role named Counter with an attribute and an increment method. Roles are not classes, so we can't call Counter->new. A role is more just a container for methods and attributes.

Slide 6

Slide 6 text

Consuming a Role class Odometer with Counter { method reset(Crook $you) { $you->break_into($self); $self->counter(0); $you->plead('innocent'); } } ϩʔϧ͸withΛ࢖ͬͯ૊ΈࠐΈ·͢ Friday, September 11, 2009 This is what consuming a role looks like. Here we have a class that needs a counter for keeping track of how many miles you have driven. "with" is the key word for consuming a role, much like "extends" is the key word for subclassing.

Slide 7

Slide 7 text

Odometer Fraud http://en.wikipedia.org/wiki/Odometer_fraud Friday, September 11, 2009 It is a very serious crime.

Slide 8

Slide 8 text

Consuming a Role class Odometer with Counter { method reset(Crook $you) { $you->break_into($self); $self->counter(0); $you->plead('innocent'); } } ͜ΕͰϩʔϧͷcounter ΞτϦϏϡʔτ͕௥Ճ͞Ε·ͨ͠ Friday, September 11, 2009 As you can see here, the role has added the counter attribute to Odometer. It also gained the increment method, which is presumably called by other parts of the system.

Slide 9

Slide 9 text

Consuming a Role class Odometer extends Widget with Counter { method reset(Crook $you) { $you->break_into($self); $self->counter(0); $you->plead('innocent'); } } ϩʔϧͱܧঝ͸ҧ͏ͱ͍͏͜ͱʹ͝஫ҙ͍ͩ͘͞ Friday, September 11, 2009 I must stress that consuming a role is not inheritance. You can still use inheritance alongside roles. What roles do are "flatten" its bits into the class.

Slide 10

Slide 10 text

class Odometer extends Widget { has counter => ( is => 'ro', isa => 'Int', default => 0, ); method increment { $self->counter($self->counter + 1); } method reset(Crook $you) { $you->break_into($self); $self->counter(0); $you->plead('innocent'); } } ϩʔϧ͸࣮ߦ࣌ʹίϐʔˍϖʔετ͢ΔΑ͏ͳ΋ͷͰ͢ Friday, September 11, 2009 The flattening is a lot like copying and pasting the code from the role into its consumer. That's even a pretty good description of what happens behind the scenes, though it doesn't happen at the strings-of-code level.

Slide 11

Slide 11 text

Class Building Blocks class Action::Throw with Action::Role::Direction with Action::Role::Item { … } class Action::Melee with Action::Role::Monster with Action::Role::Direction { … } ϩʔϧͷΑ͞͸࠶ར༻ੑʹ͋Γ·͢ Friday, September 11, 2009 My favorite feature of roles is their reusability. One way to look at roles is that they are class building blocks.

Slide 12

Slide 12 text

Class Building Blocks class Action::Throw with Action::Role::Direction with Action::Role::Item { … } class Action::Melee with Action::Role::Monster with Action::Role::Direction { … } ͲͪΒ΋ํ޲ؔ܎ͷ໾ׂΛ࣋ͭ͜ͱ͕Θ͔Γ·͢Ͷ Friday, September 11, 2009 If we factor out common behavior, we get to name that chunk of behavior, and reuse all that code. We know that these two actions have something to do with direction.

Slide 13

Slide 13 text

Class Building Blocks class Action::Throw extends Action::Direction extends Action::Item { … } class Action::Melee extends Action::Monster extends Action::Direction { … } ଟॏܧঝ͕Α͘ͳ͍ͷ͸͝ଘ͡Ͱ͢ΑͶʁͶʁ Friday, September 11, 2009 You might be wondering how any of this is better than multiple inheritance. Surely you know multiple inheritance is evil, right? RIGHT? By the way, did anyone here even know how to use multiple inheritance with MooseX::Declare? I sure didn't until I wrote this slide.

Slide 14

Slide 14 text

class Action::Monster { has monster => (…); method name { $self->monster->name } } Multiple Inheritance ଟॏܧঝ͕Α͘ͳ͍ྫΛ঺հ͠·͠ΐ͏ Friday, September 11, 2009 Let's look at an example of why multiple inheritance is maligned. Here we have a class with a "name" method.

Slide 15

Slide 15 text

class Action::Direction { has direction => (…); } Multiple Inheritance ͪ͜ΒͷΫϥεʹ͸direction͔͋͠Γ·ͤΜ Friday, September 11, 2009 Now we have another class. It just has a direction.

Slide 16

Slide 16 text

class Action::Melee extends Action::Direction extends Action::Monster { … } Multiple Inheritance ͍·͸྆ํܧঝͯ͠΋໰୊͋Γ·ͤΜ͕… Friday, September 11, 2009 And now we have a class that inherits from both of those. So everything is fine right now. But at some point the requirements change.

Slide 17

Slide 17 text

class Action::Direction { has direction => (…); method name { $self->direction->name } } Multiple Inheritance ͜͜ʹnameϝιου͕ඞཁʹͳͬͨ৔߹ Friday, September 11, 2009 We need a "name" method in Action::Direction for some other class.

Slide 18

Slide 18 text

$melee->name Multiple Inheritance Կͷܯࠂ΋ͳ͘nameͷҙຯ͕มΘͬͯ͠·͍·͢ Friday, September 11, 2009 This used to return monster name. Now this has changed without warning to the direction name, because Action::Direction is the leftmost parent. I hope your tests are very thorough! This is a huge pain to debug. Code reuse and cleanliness is often not worth this pain, so we avoid multiple inheritance.

Slide 19

Slide 19 text

class Action::Melee with Action::Direction with Action::Monster { … } Multiple Roles ϩʔϧͷ৔߹͸ܧঝͱ͸ҧͬͯ Friday, September 11, 2009 If these were roles instead, something very different happens.

Slide 20

Slide 20 text

Due to a method name conflict in roles 'Action::Direction' and 'Action::Monster', the method 'name' must be implemented or excluded by 'Action::Melee' Multiple Roles Moose͕͜ͷΑ͏ͳΤϥʔΛग़ͯ͘͠Ε·͢ Friday, September 11, 2009 Once we add the "name" method to Direction, we get this error. I'll talk about what it means soon, but one nice thing about this error is Moose throws it..

Slide 21

Slide 21 text

AT COMPILE TIME! ͔͠΋ɺίϯύΠϧ࣌ʹ! Friday, September 11, 2009 ...immediately. You don't have to wait for the class to be instantiated, or the "name" method to be called. Moose throws conflict error at "with" time.

Slide 22

Slide 22 text

class Action::Melee with Action::Direction with Action::Monster { … } Conflict Resolution ΋ͬͱ΋ɺ͜ͷΤϥʔΛճආ͢Δํ๏΋͋Γ·͢ Friday, September 11, 2009 So this code is an error. But as alluded to in the error message, we can resolve this conflict. There are several ways to do this.

Slide 23

Slide 23 text

class Action::Melee with Action::Direction with Action::Monster { method name { "melee" } } Conflict Resolution ΫϥεʹϝιουΛ ఆٛͯ͠͠·͏ͷ͸ͻͱͭͷखͰ͢ Friday, September 11, 2009 The class could define its own method. When a class defines a method and a role it consumes defines the same method, the class wins. So this resolves the conflict by overriding the "name" methods from the two roles. It's kind of like plugging your ears and yelling that everything is okay. But sometimes that really is all you need.

Slide 24

Slide 24 text

class Action::Melee with Action::Direction with Action::Monster excludes name { … } Conflict Resolution িಥ͢ΔϝιουΛ૊Έࠐ·ͳ͍ख΋͋Γ·͢ Friday, September 11, 2009 Another option is to exclude one of the conflicting methods. This way, Direction's "name" method is the one that is added to Melee. Obviously you can only use this where it makes sense. It probably doesn't make sense to use it in this example. XXX: This syntax doesn't actually work yet as of 2009-08-22. A failing test has been submitted!

Slide 25

Slide 25 text

class Action::Melee with Action::Direction alias { name => 'direction_name' } with Action::Monster alias { name => 'monster_name' } { method name { loc '%1 (at %2)', $self->monster_name, $self->direction_name; } } 2ͭͷϝιουΛ݁߹ͯ͠͠·͏ख΋͋Γ·͢ Friday, September 11, 2009 Another option is to combine the two methods. Here we disambiguate the two conflicting "name" methods, then use them in our own "name" method which serves both roles well enough. I've found that this is usually the best solution.

Slide 26

Slide 26 text

Due to a method name conflict in roles 'Action::Direction' and 'Action::Monster', the method 'name' must be implemented or excluded by 'Action::Melee' Conflict Resolution ͜ͷΤϥʔͷҙຯ͸Θ͔Γ·ͨ͠Ͷ Friday, September 11, 2009 So now we know what this error message means.

Slide 27

Slide 27 text

Due to a method name conflict in roles 'Action::Direction' and 'Action::Monster', the method 'name' must be implemented or excluded by 'Action::Melee' Conflict Resolution িಥͨ͠ϝιου͸ΦʔόʔϥΠυͰ͖·͢ Friday, September 11, 2009 You can override the conflicting methods in the class, with or without reusing each conflicting method.

Slide 28

Slide 28 text

Due to a method name conflict in roles 'Action::Direction' and 'Action::Monster', the method 'name' must be implemented or excluded by 'Action::Melee' Conflict Resolution ෆཁͳํΛഉআͯ͠΋͔·͍·ͤΜ Friday, September 11, 2009 Or, if it makes sense, just exclude one of the methods so that it's no longer a conflict.

Slide 29

Slide 29 text

class Action::Melee extends Action::Direction extends Action::Monster excludes name { … } Conflict Resolution ଟॏܧঝͰ͜Μͳ͜ͱͰ͖·͔͢ʁ Friday, September 11, 2009 Try that with multiple inheritance. Yeah right!

Slide 30

Slide 30 text

"Roles are the sound of diamond inheritance that people have stopped banging their heads against." - hdp ϩʔϧͷ৔߹μΠϠϞϯυܧঝ໰୊͸ى͜Γ·ͤΜ Friday, September 11, 2009 Because of the flattening property of roles, and because of this conflict detection and resolution, the diamond inheritance problem doesn't apply to roles. Any ambiguity is a compile-time error, and the programmer has several tools to resolve such ambiguities. Role composition is much more pleasant to work with than multiple inheritance.

Slide 31

Slide 31 text

Required Methods role Action::Role::Monster { method monster { $self->tile->monster } method has_monster { $self->tile->has_monster } } ಛఆͷϝιουΛඞཁͱ͢ΔϩʔϧΛ ߟ͑ͯΈ·͠ΐ͏ Friday, September 11, 2009 Suppose we have a role that assumes a particular method exists in each of its consumers. In this case, we're calling "tile" even though we don't know for sure that each consumer will have that method.

Slide 32

Slide 32 text

Required Methods class Action::Chat with Action::Role::Monster { method run { print "woof" } } ͜ͷίʔυࣗମʹ໰୊͸͋Γ·ͤΜ Friday, September 11, 2009 This code will work fine. We consume the role, Action::Chat gets the new methods, and everything is hunky-dory.

Slide 33

Slide 33 text

Required Methods $chat->has_monster Ͱ΋ɺ࣮ߦ࣌ʹ͜ͷϝιουΛ ݺͿͱΤϥʔʹͳΓ·͢ Friday, September 11, 2009 But at runtime when we try to call this method, we get an error.

Slide 34

Slide 34 text

Required Methods Can't locate object method "tile" via package "Action::Chat" tileͱ͍͏ϝιου͕ඞཁͳΒίʔυͰ໌ݴ͢΂͖Ͱ͢ Friday, September 11, 2009 This sucks. The author of the Monster role demands that consumers have a "tile" method. Perhaps the role's documentation does notify you that consumers must have it, but who even reads documentation any more? It would be nice to be able to codify this requirement.

Slide 35

Slide 35 text

Required Methods role Action::Role::Monster { requires 'tile'; method monster { $self->tile->monster } method has_monster { $self->tile->has_monster } } ࣗ෼Ͱ༻ҙ͍ͯ͠ͳ͍ϝιου͸ ૬खʹ༻ҙͤ͞·͠ΐ͏ Friday, September 11, 2009 And, of course, you can. If a role calls methods that it doesn't provide itself, the role should require them.

Slide 36

Slide 36 text

Required Methods 'Action::Role::Monster' requires the method 'tile' to be implemented by 'Action::Chat' ͦ͏͢Ε͹ίϯύΠϧ࣌ʹΤϥʔΛग़ͤ·͢ Friday, September 11, 2009 And like a conflict, we get this error at compile time. Much better than a method-missing error at runtime when we try to call ->tile. The Moose team really likes the fail-fast principle.

Slide 37

Slide 37 text

role Scan { requires 'entries'; method ages { map { $_->age } $self->entries } } Default Implementation ඞਢϝιουΛ࢖ͬͯผͷϝιουΛ࡞ΔྫͰ͢ Friday, September 11, 2009 Here we have a role that requires an "entries" method, then builds on top of it with another method "ages".

Slide 38

Slide 38 text

class Backend::Hash with Scan { method entries { values %{ $self->storage } } } Default Implementation ΤϯτϦΛϋογϡʹೖΕΔόοΫΤϯυΛ ༻ҙͯ͠Έ·͢ Friday, September 11, 2009 We have an example backend that stores its entries in a hash table. We provide the "entries" method that the Scan role requires. That role gives us an "ages" method that calls "entries". Pretty straightforward use of roles.

Slide 39

Slide 39 text

class Backend::DBI with Scan { method entries { $self->q("SELECT * …"); } method ages { $self->q("SELECT age …"); } } Default Implementation ϝιουΛఆٛͯ͠͠·͏ྫ͸ িಥճආҎ֎Ͱ΋༗ޮͰ͢ Friday, September 11, 2009 When I was describing conflict resolution I mentioned how a method defined by the class wins over a method pulled in from a role. That's useful even outside of conflict resolution.

Slide 40

Slide 40 text

class Backend::DBI with Scan { method entries { $self->q("SELECT * …"); } method ages { $self->q("SELECT age …"); } } Default Implementation σϑΥϧτͷϝιου͸࣮૷ྫͷ໾ׂ΋͠·͢ Friday, September 11, 2009 The ages method we define is doing a lot less work than the default implementation provided by the role would do. We don't need to pull in every field of every entry then pick out the values for age. However, the role's implementation is a good default that would be useful for a lot of these backends. Method overrides permit reuse but allow optimizations or alternate implementations where needed.

Slide 41

Slide 41 text

class Backend::DBI with Scan { method entries { $self->q("SELECT * …"); } method ages { $self->q("SELECT age …"); } } Default Implementation ͜ͷ৔߹ͳͥScanΛ߹੒͢ΔͷͰ͠ΐ͏͔ Friday, September 11, 2009 You might be asking why we bother to consume Scan even though we don't actually pull in any methods or attributes from it. That's next!

Slide 42

Slide 42 text

"The more I use roles, the less I understand why anyone would want to use the inheritance model." - Ovid ϩʔϧΛ࢖͏ͱܧঝϞσϧΛ ࢖͍͕ͨΔਓͷؾ͕஌Εͳ͘ͳΔ Friday, September 11, 2009 Roles have so many excellent features that I am starting to agree with this (admittedly radical) viewpoint myself.

Slide 43

Slide 43 text

Role = Type Allomorphism ϩʔϧ͸ܕγεςϜͷҰ෦ʹͳΓ͑·͢ Friday, September 11, 2009 Allomorphism is a fancy word that means a few things. For one, roles can be part of the type system. It's a lot like duck typing, but more explicit.

Slide 44

Slide 44 text

Role = Semantics Allomorphism ϩʔϧ͸ηϚϯςΟΫε΋҉ࣔ͠·͢ Friday, September 11, 2009 Allomorphism also means that a role implies semantics. Basically, every method implemented or required by the role must implement some specified behavior.

Slide 45

Slide 45 text

Role = Semantics Allomorphism Role = Type ҙຯ͢Δͱ͜Ζ͸ಉ͡Ͱ͔͢Β·ͱΊͯઆ໌͠·͢ Friday, September 11, 2009 These imply similar consequences, so I'm going to explain them together.

Slide 46

Slide 46 text

role Interface::Nonblocking { requires 'read', 'write'; } Allomorphism ϩʔϧ໊͔Βread΍writeΛ Ͳ͏ॻ͚͹Α͍͔Θ͔Γ·͢ Friday, September 11, 2009 Here we have a role that requires "read" and "write" methods from each of its consumers. Given the name of the role, we can guess that the role requires these methods to be nonblocking.

Slide 47

Slide 47 text

class Nonblocking::Socket with Interface::Nonblocking { method read { … } method write { … } } Allomorphism Ϋϥεఆٛ࣌ʹ͸ϊϯϒϩοΩϯάͷཁ݅Λ ຬͨ͢Α͏ʹ͠·͢ Friday, September 11, 2009 Here we define a class that does the nonblocking interface. Nonblocking::Socket promises that its read and write methods fulfill the socket's nonblocking requirements.

Slide 48

Slide 48 text

class Blocking::Socket { method read { … } method write { … } } Allomorphism ͪ͜Β͸ϒϩοΫ͢ΔΫϥεͷྫͰ͢ Friday, September 11, 2009 Here's another class, one whose read and write methods do block. Even though it fulfills the method name requirements of Interface::Nonblocking, this class would be lying if it declared that it does the role.

Slide 49

Slide 49 text

my $s = shift; $s->does('Interface::Nonblocking') or confess 'No blocking!'; $s->write('͓͸Α͏ʂ'); Allomorphism ͜͜Ͱ͸ϩʔϧΛຬ͔ͨ֬͢ೝ͍ͯ͠·͢ Friday, September 11, 2009 We can ask an object if it does a particular role. This will die if we try to use a Blocking::Socket here. This is better than checking "isa" because we actually care about capability. We don't care what $s's class is, or what its ancestors are. Any class can declare that it does the Interface::Nonblocking role, as long as it fulfills the role's contract.

Slide 50

Slide 50 text

my $s = shift; $s->can('read') && $s->can('write') or confess 'Need an interface'; $s->write('͓͸Α͏ʂ'); Duck Typing μοΫλΠϐϯάͩͱηϚϯςΟΫε͕Θ͔Γ·ͤΜ Friday, September 11, 2009 Duck typing fulfills this same need. We also don't care about what class $s is, as long as it has the methods we want. However, the problem with duck typing is that merely having a set of methods does not imply semantics. Perhaps read and write are actually going to do text-to-speech and printing a term paper. Or worse, they might block.

Slide 51

Slide 51 text

my $s = shift; $s->does('Interface::Nonblocking') or confess 'No blocking!'; $s->write('͓͸Α͏ʂ'); Allomorphism write͕ϒϩοΫ͠ͳ͍ͱΘ͔Δͷ͸ ϩʔϧͷ͓͔͛Ͱ͢ Friday, September 11, 2009 In any case, because the Interface::Nonblocking role requires the write method, we know that $s will not only have it, but we know it will not block.

Slide 52

Slide 52 text

has connection => ( isa => 'Nonblocking::Socket', ); Allomorphism ΞτϦϏϡʔτͷ৔߹΋΄ͱΜͲಉ͡Ͱ͕͢ Friday, September 11, 2009 Moose has support for allomorphism in attributes. Instead of demanding that connection be a particular class...

Slide 53

Slide 53 text

has connection => ( isa => 'Nonblocking::Socket', does => 'Interface::Nonblocking', ); Allomorphism ಛఆͷϩʔϧΛຬͨ͢ίωΫγϣϯͷ ஋ΛཁٻͰ͖·͢ Friday, September 11, 2009 … we can demand that the value of connection does a particular role.

Slide 54

Slide 54 text

"A role-aware type system allows you to express yourself with better genericity." - chromatic ΠϯλϑΣʔε͑͞ຬͨͤ͹Α͍ͱ͍͏ͷ͕ϙ ΠϯτͰ͢ Friday, September 11, 2009 chromatic's point here is that you can stop caring about hierarchy and start caring about capabilities. Allomorphism means you don't need to subclass someone's crack-fueled module. You need only fulfill its crack-fueled interface. It's OOP freedom.

Slide 55

Slide 55 text

class WWW::Mechanize::TreeBuilder extends WWW::Mechanize { has tree => ( … ); around _make_request { … } } Extensions ϞδϡʔϧΛ֦ு͢Δͱ͖͸;ͭ͏ αϒΫϥεʹ͠·͢ΑͶʁ Friday, September 11, 2009 Here's a quick tip for you. Say you're extending a module. Ordinarily you'd write a subclass, right?

Slide 56

Slide 56 text

class Test::WWW::Mech::TreeBuilder extends Test::WWW::Mechanize { has tree => ( … ); around _make_request { … } } Extensions ςετͷํ΋αϒΫϥεʹ͠·͢ Friday, September 11, 2009 You write good tests, so you subclass its test subclass too.

Slide 57

Slide 57 text

class WWW::Mech::Cache::TreeBuilder extends WWW::Mechanize::Cached { has tree => ( … ); around _make_request { … } } Extensions ΄͔ͷαϒΫϥε΋ʁ Mechʹύον౰ͯͨํ͕Α͘ͳ͍ʁ Friday, September 11, 2009 You also have to subclass all the other subclasses you use. Maybe it'd be better to just monkeypatch WWW::Mechanize. Who'd know?

Slide 58

Slide 58 text

BAD! Extensions ͦ͏͍͏͜ͱ͸ɺͪ͠Ό͍͚·ͤΜʂ Friday, September 11, 2009 No! Don't do it! You'll screw it up for anyone else who happens to use that module in your codebase.

Slide 59

Slide 59 text

role WWW::Mechanize::TreeBuilder { has tree => ( … ); around _make_request { … } } Extensions ֦ுΛϩʔϧʹͯ͠͠·͍·͠ΐ͏ Friday, September 11, 2009 Just make your extension a role.

Slide 60

Slide 60 text

class Test::WWW::Mech::TreeBuilder extends Test::WWW::Mechanize with WWW::Mechanize::TreeBuilder {} Extensions ͋ͱ͸ϩʔϧΛద༻͢Δ͚ͩɻলུͳΜͯ͋Γ·ͤΜ Friday, September 11, 2009 Now you can apply this role to the existing subclasses of the module. There's no dot-dot-dot in the braces here. This is a full class definition.

Slide 61

Slide 61 text

class WWW::Mech::Cache::TreeBuilder extends WWW::Mechanize::Cached with WWW::Mechanize::TreeBuilder {} Extensions ֦ு෦෼͸͢΂ͯϩʔϧͷதͰ͢ɻ͖Ε͍Ͱ͠ΐʁ Friday, September 11, 2009 That's really it. You don't need any other code. All the extension code is in your role. Nice and clean.

Slide 62

Slide 62 text

Roles > Inheritance Extensions ͳΔ΂͘ܧঝ͠ͳ͍Ͱϩʔϧʹ͢Δ͜ͱ Friday, September 11, 2009 There, I said it! If you can implement your extension as a role, do it. Use inheritance sparingly.

Slide 63

Slide 63 text

role Rootey { method su { … } } $jrock = User->new("jrockway"); $shawn = User->new("sartak"); Rootey->meta->apply($shawn); $shawn->su; # ok $jrock->su; # dies! Role -> Object ϩʔϧΛಛఆͷΦϒδΣΫτʹ ద༻͢Δ͜ͱ΋Ͱ͖·͢ Friday, September 11, 2009 I don't want to spend much time on this, but you can apply a role to a particular object. It doesn't have to be a full-blown class. It also won't affect any other objects. Which is good, because I wouldn't trust Jon with root.

Slide 64

Slide 64 text

Plugins MooseX::Object::Pluggable MooseX::Traits ࡢ೔࿩ͨ͠Α͏ʹɺϩʔϧ͸ϓϥάΠϯʹ΋࢖͑·͢ Friday, September 11, 2009 Roles are nice for plugins too. I covered this heavily in my API Design talk. The idea is each plugin is just a role. These two modules make roles-as-plugins very easy. http://sartak.org/talks/yapc-asia-2009/api-design/

Slide 65

Slide 65 text

Classes are Nouns Message User Service Request Response Ϋϥεͱ͍͏ͷ͸͍ΘΏΔ໊ࢺͰ͢ Friday, September 11, 2009 To give you an idea of how to think about roles, here's a pretty simple metaphor that works for me. Classes are nouns.

Slide 66

Slide 66 text

Methods are Verbs send_message add_user connect_to_service handle_request serve_response ϝιου͸ಈࢺͰ͢Ͷ Friday, September 11, 2009 Methods are verbs. They are simple behaviors. They do things.

Slide 67

Slide 67 text

Roles are Adjectives HasPlugins RequiresPlugins Throwable Reblessing ExtendsObject::User ϩʔϧ͸ܗ༰ࢺͰ͢ Friday, September 11, 2009 Roles are adjectives. These are all good role names. If you think of some piece of behavior as an adjective, that's a good sign that it can be factored out as a role.

Slide 68

Slide 68 text

role Scan { requires 'entries'; method ages { map { $_->age } $self->entries } } Parameterized Roles ͜Ε͸·͞ʹύϥϝʔλ͕ඞཁͳϩʔϧͷྫͰ͢ Friday, September 11, 2009 This is a very specific kind of parameterized role. Each consumer parameterizes the "ages" method by providing an "entries" method. But that's not really what I'm talking about when I say parameterized role. If you can do this, do it. If you need something more advanced...

Slide 69

Slide 69 text

Parameterized Roles MooseX::Role::Parameterized ࢲͷϞδϡʔϧͰ͸͜Ε͕Ұ൪Mooseʹߩݙ͍ͯ͠·͢ Friday, September 11, 2009 Parameterized roles are my biggest contribution to Moose. I'm happy with how they came out.

Slide 70

Slide 70 text

Parameterized Roles role Counter (Int :$default = 0) { has counter => ( is => 'ro', isa => 'Int', default => $default, ); sub increment { my $self = shift; $self->counter($self->counter + 1); } } ͜ΕΛ࢖͏ͱ૊ΈࠐΉଆ͕σϑΥϧτ஋Λ એݴͰ͖·͢ Friday, September 11, 2009 Here we have our old Counter role, but now each consumer can declare what default it wants for the attribute. If they choose nothing, they get the default of 0. rafl++ added parameterized role support to MooseX::Declare recently, so my examples get to look much nicer.

Slide 71

Slide 71 text

Parameterized Roles 66 class Odometer with Counter(default => 10000) { … } Ҿ਺Λ౉͍ͯ͠Δ͜ͱҎ֎ɺ࢖͍ํ͸΄΅ಉ͡Ͱ͢ Friday, September 11, 2009 Using a parameterized role is pretty much the same. You just pass in the named arguments to "with". This was an example of parameterizing an attribute, and is probably the most common use of p-roles.

Slide 72

Slide 72 text

Parameterized Roles 66 role Speedometer (Str :$method) { around $method { my $start = time; $orig->(@_); print "$method " . (time - $start); } } ͜Ε͸ಛఆͷϝιουʹϓϩϑΝΠϥΛ ௥Ճ͢ΔϩʔϧͰ͢ Friday, September 11, 2009 Here we have a role that wraps any method you give it with a bit of profiling code. This is sort of the inverse of "requires". Instead of the role telling you what method you need, you tell the role what method it needs to instrument. Someone once praised this type of p-role usage as resembling macros.

Slide 73

Slide 73 text

Parameterized Roles 66 role WWW::Search (Str :$engine) { method search (Str $query) { if ($engine eq "Google") { REST::Google::Search->… } elsif ($engine eq "Yahoo!") { Yahoo::Search->… } elsif ($engine eq "Bing") { die "seriously?"; } # post-process @results } } ύϥϝʔλ෇͖ϩʔϧͷ௕ॴ͸ίʔυͷ ࠶ར༻ੑ͕ߴ·Δ͜ͱ Friday, September 11, 2009 One really nice thing about parameterized roles is that they improve code reuse. All the consumer has to do is inform the role of which search engine they want to use. The role takes care of the rest. This means each consumer writes less code, because the parameterized role can build up more structure.

Slide 74

Slide 74 text

Parameterized Roles 66 package Valid::Adding; use MooseX::Role::Parameterized; role { $consumer->does('Adding') or die; $consumer->add(2, 6) == 8 or die; method safe_add => sub { … }; }; ͜ͷΑ͏ʹ௥ՃͷόϦσʔγϣϯʹ΋ར༻Ͱ͖·͢ Friday, September 11, 2009 Here's another use case that was raised recently. The parameterized role could perform additional validation on each consumer. This is like "requires" for method names, but stronger. This is what pre-MooseX::Declare parameterized roles look like, due to language constraints.

Slide 75

Slide 75 text

Roles Class building blocks Conflicts, resolution Required methods Allomorphism (types) Extensions, Plugins "adjectives" Έͳ͞Μ΋ͥͻϩʔϧΛ࢖ͬͯΈ͍ͯͩ͘͞ Friday, September 11, 2009 Roles are all of these things. I hope I've convinced you to use roles in your next project's design. Thank you!

Slide 76

Slide 76 text

Thanks to my reviewers Vlad Dogsby Thomas Figg Alexander Hoffer Stevan Little Lucas Oman Simon Pollard Friday, September 11, 2009 Thanks to these people who have reviewed my slides and offered excellent advice.

Slide 77

Slide 77 text

Thanks to my translator Kenichi Ishigaki charsbar++ Friday, September 11, 2009 Thank you to Ishigaki-san for translating my slides!

Slide 78

Slide 78 text

See Also Moose::Manual::Roles "The Why of Perl Roles" "Eliminating Inheritance via Smalltalk-Style Traits" "Traits - Composing Classes from Behavioral Building Blocks" Friday, September 11, 2009 http://search.cpan.org/perldoc?Moose::Manual::Roles http://www.modernperlbooks.com/mt/2009/04/the-why-of-perl-roles.html http://use.perl.org/~Ovid/journal/39404 http://scg.unibe.ch/archive/phd/schaerli-phd.pdf