From 8fa994464b73131f008fa28fd2040cbd62cbca17 Mon Sep 17 00:00:00 2001 From: chromatic Date: Thu, 7 Jan 2010 16:40:11 -0800 Subject: [PATCH] Minor edits to the Roles chapter; some author notes. --- src/roles.pod | 324 ++++++++++++++++++++++++++++---------------------- 1 file changed, 185 insertions(+), 139 deletions(-) diff --git a/src/roles.pod b/src/roles.pod index f69a7c6..0a96724 100644 --- a/src/roles.pod +++ b/src/roles.pod @@ -1,9 +1,14 @@ =head0 Roles -The following program demonstrates a simple and pluggabe IRC bot framework -along with an implementation of a few simple commands. It demonstrates the -use of roles, re-usable pieces of functionality that can be safely composed -into a class at compile time or mixed in per-object at runtime. +X +X + +A I is a standalone, named, reusable unit of behavior. You can compose a +role into a class at compile time or add it to an individual object at runtime. + +That's an abstract definition best explained by an example. This program +demonstrates a simple and pluggable IRC bot framework which understands a few +simple commands. =begin programlisting @@ -24,6 +29,7 @@ into a class at compile time or mixed in per-object at runtime. role KarmaTracking { has %!karma-scores; + multi method on-message($sender, $msg where /^karma /) { if %!karma-scores{$} -> $karma { return $ ~ " has karma $karma"; @@ -32,9 +38,11 @@ into a class at compile time or mixed in per-object at runtime. return $ ~ " has neutral karma"; } } + multi method on-message($sender, $msg where / '++'/) { %!karma-scores{$}++; } + multi method on-message($sender, $msg where / '--'/) { %!karma-scores{$}--; } @@ -42,11 +50,13 @@ into a class at compile time or mixed in per-object at runtime. role Oping { has @!whoz-op; + multi method on-join($nick) { if $nick eq any(@!whoz-op) { return "/mode +o $nick"; } } + multi method on-message($sender, $msg where /^trust /) { if $sender eq any(@!whoz-op) { push @!whoz-op, $; @@ -71,6 +81,7 @@ into a class at compile time or mixed in per-object at runtime. role AnswerIfTalkedTo { method bot-nick() { ... } + method process($raw-in) { if $raw-in ~~ // { self.*on-join($); @@ -106,48 +117,56 @@ into a class at compile time or mixed in per-object at runtime. =end programlisting =head1 What is a role? -In the previous chapters, you will have discovered classes and grammers. A -role is another type of package. Like classes and grammars, you can put -methods (including named regexes) and attributes into a role. However, a -role cannot be used on its own. Instead, roles are designed to be incorporated -into objects, classes and grammars. - -In traditional object orientation, classes are used both as a mechanism for -code re-use and to represent entities in our system by providing a model from -which we can create instances. These two tasks are somewhat in tension; for -optimal re-use, classes should be small, but in order to represent a complex -entity with many behaviors, classes tend to grow large. - -In Perl 6, classes retain the responsibility for instance management, whereas -the task of code re-use is handled by roles. A role contains the methods and -attributes required to provide a small and re-usable piece of functionality. -Classes can then be built up out of roles, using a safe mechanism called -flattening composition. A role can also be mixed in to an individual object. -You will have spotted both of these taking place in the example code. Later -on in the chapter, you'll also see how roles can be parameterized; they are -also Perl 6's mechanism for providing generic programming, along the lines of -generics in C# and Java, or templates in C++. + +Previous chapters have explained classes and grammers. A role is another type +of package. Like classes and grammars, a role can contain methods (including +named regexes) and attributes. However, a role cannot stand on its own. To use +a role, you must incorporate it into an object, a class, or a grammar. + +In traditional object orientation, classes perform two tasks. They represent +entities in the system by providing models from which to create instances. +They also provide a mechanism for code re-use. These two tasks are somewhat in +tension. For optimal re-use, classes should be small, but in order to represent +a complex entity with many behaviors, classes tend to grow large. + +X +X + +Perl 6 classes retain the responsibility for instance management. Roles handle +the task of code reuse. A role contains the methods and attributes required to +provide a named, reusable unit of behavior. Building a class out of roles uses +a safe mechanism called I. You may also apply a role +to an individual object. Both of these design techniques appear in the example +code. + +Roles may also support parameters. This helps Perl 6 provide generic +programming, along the lines of generics in C# and Java, or templates in C++. =head1 Compile Time Composition -Take a look at the C class definition. The body is empty; the class -does not define any attributes or methods of its own. The class inherits from -C, using the C trait modifier - something familiar from ealier -chapters - but it also uses the C trait modifier to compose two roles + +X +X +X +X + +Take a look at the C class definition. The body is empty; the +class defines no attributes or methods of its own. The class inherits from +C, using the C trait modifier -- something familiar from ealier +chapters -- but it also uses the C trait modifier to compose two roles into the class. -The process of role composition simply involves taking the attributes and -methods defined in each role that is being composed and "copying" them into -the class. Therefore, after composition, it is as if the things defined in -the role bodies were defined within the class itself. This is part of the -flattening property: after we have performed the composition, aside from -being able to query the class to know if a role was composed into it at some -point, the roles are no longer significant. Therefore, if introspection was -used to query the methods of the C class, it would report that it -has both a C method and an C multi method. +The process of role composition is simple. Perl takes the attributes and +methods defined in each role and copies them into the class. After composition, +the class appears as if it had defined those behaviors itself. This is part of +the flattening property: after composing a role into the class, the roles in +and of themselves are only important when querying the class to determine I +it performs the role. Querying the methods of the C class through +introspection will report that the class has both a C method and an +C multi method. -If that was the whole story, roles would not seem to offer anything much -over inheritance or mix-ins. The point things get interesting is what -happens if there are conflicts. Consider the following class definition. +If this were all that roles provided, they'd have few advantages over +inheritance or mixins. Roles get much more interesting in the case of a +conflict. Consider the class definition: =being programlisting @@ -156,21 +175,21 @@ happens if there are conflicts. Consider the following class definition. =end programlisting -Here, both the C and C roles provide a method named -C. It's interesting to note at this point that these are semantically -different options, and in a very real world sense the two behaviors conflict. -It's also a conflict in the eyes of the role composer, which will give a -compile-time error, indicating the conflict and asking the programmer to -provide a resolution. This is rather different from multiple inheritance or -mix-in mechanisms, whereas the order in which we inherit or mix in decides -which method wins. In role composition, all possible roles are considered -equals. - -So what can be done if there is a conflict? In this case, it likely does not -make sense to compose both of the roles. Therefore a programmer here is most -likely to realize their mistake and pick which of the two behaviors they want. -An alternative way to resolve a conflict is to write a method with the same -name in the class body itself. +Both the C and C roles provide a method named +C. Even though they share a name, the methods perform semantically +different behaviors--behaviors which conflict. The role composer will produce +a compile-time error about this conflict, asking the programmer to provide a +resolution. + +Multiple inheritance and mixin mechanisms rarely provide this degree of +conflict resolution. In those situations, the order of inheritance or mixin +decides which method wins. All possible roles are equal in role composition. + +What can you do if there is a conflict? In this case, it makes little sense to +compose both of the roles into a class. The programmer here has made a mistake +and should choose to compose only one role to provide the desired behavior. An +alternative way to resolve a conflict is to write a method with the same name +in the class body itself: =being programlisting @@ -187,24 +206,25 @@ will then disregard all of the (possibly conflicting) ones from the roles. Put simply, methods in the class always win. =head2 Multi-methods and composition -Sometimes, it's OK if there are multiple methods of the same name, provided -they have different signatures and therefore can be distinguished by the -normal multi-dispatch mechanism. Therefore, if methods from different roles -with matching names declare themselves as multi methods, they will not be -in conflict. Instead, the candidates from all of the roles will be combined -together and all of them will be composed into the class. + +X + +Sometimes it's okay to have multiple methods of the same name, provided they +have different signatures such that the multidispatch mechanism can distinguish +between them. Multi methods with the same name from different roles will not +be in conflict. Instead, the candidates from all of the roles will combine +together during composition into the class. If the class provides a method of the same name that is also multi, then the -candidates from the class will also be included. On the other hand, if the -class has a method of the same name that is not declared as a multi, then the -multis from the roles will be disregarded and the method in the class - as -usual - will win. +multi candidates from the class will be included. On the other hand, if the +class has a method of the same name that is I declared as a multi, then +the method in the class alone--as usual--will win. This is the mechanism by which a class that composes both, for example, the -KarmaTracking role and the Oping role would end up having the candidates that -both roles provide for the C method. As a class ends up getting -composed of more than just a couple of roles, it may be preferable to use an -alternative syntax that allows you to list the roles in the class body. +C role and the C role would end up having the candidates +that both roles provide for the C method. As a class ends up +composing more than a couple of roles, it may be preferable to use an +alternative syntax that allows you to list the roles in the class body: =begin programlisting @@ -216,9 +236,16 @@ alternative syntax that allows you to list the roles in the class body. =end programlisting -=head2 Calling all cadidates -In the C methods of the roles C and C, -a slightly different method calling syntax is used: +=head2 Calling all candidates + +=for author + +This needs an index tag or two, but I'm not sure which. + +=end for + +The C methods of the roles C and C use +a modified syntax for calling methods: =begin programlisting @@ -226,27 +253,26 @@ a slightly different method calling syntax is used: =end programlisting -The use of C<.*> instead of just a simple C<.> slightly changes the semantics -of the method dispatch. Just as the C<*> quantifier in regexes means "zero or -more", the C<.*> dispatch operator will call zero or more matching methods. -This means that if there are no C multi candidates that match, then -the call does not result in an error. Additionally, C<.*> is greedy - once again -like the C<*> quantifier in regexes. This means that it doesn't stop after one -matching method has been called, but instead calls all candidates that it can, -which may have been found either through multiple dispatch and/or by searching -the inheritance hierarchy. - -There are two other variants, C<.+> which greedily calls all methods but dies -if there is not at least one method that matches, and C<.?>, which calls one -method just like C<.>, but if none is found just returns a C rather -then throwing an exception. You probably won't use C<.*> and C<.+> all that -often, but they are especially handy when doing event driven programming, -like in our example here. C<.?> plays a useful role when doing per-object -mix-ins, however. +The use of C<.*> instead of C<.> changes the semantics of the method dispatch. +Just as the C<*> quantifier in regexes means "zero or more", the C<.*> dispatch +operator will call zero or more matching methods. If no C multi +candidates match, the call will not produce an error. If more than one +C multi candidate matches, Perl will call all of them, whether +found by multiple dispatch, searching the inheritance hierarch, or both. + +There are two other variants. C<.+> greedily calls all methods but dies if +there is not at least one method that matches. C<.?>, which tries to call one +method, but returns a C rather then throwing an exception. The +zero-or-more and one-or-more dispatch forms are somewhat rare, but very useful +in event driven programming. One-or-failure is very useful when dealing with +per-object role application. =head2 Expressing requirements -In the role C, the method C is declared, but -no implementation is given; it's just a method stub. + +X +X + +The role C declares a stub for the method C, but never gives an implementation. =begin programlisting @@ -254,67 +280,87 @@ no implementation is given; it's just a method stub. =end programlisting -In the context of a role, this is your way of declaring that when the role is -composed, a method C must be provided. It may be provided either by -the class itself, a parent class (which is true in this case - IRCBot defines -an attribute C<$!bot-nick> along with an accessor method) or another role. +In the context of a role, this techniques declares that any class which +composes this role must somehow provide a method named C. The class +itself may provide it, another role must provide it, or a parent class must +provide it. IRCBot does the latter; it IRCBot defines an attribute +C<$!bot-nick> along with an accessor method. + +=for author + +What about C? -You are not required to make explicit what methods your role depends on. Doing -so, however, means that the problem can be detected at compile time, when the -class is being composed. Leaving it out means that you won't know about the -missing method until runtime, when another method in your role attempts to -call it. +=end for + +You are not I to make explicit the methods on which your role +depends. If you do so, the role composer can detect any errors at compile +time. If you do not, the problem will only appear at runtime, when and if +something attempts to call the missing method. =head1 Runtime Mixins -Often, an object just being an instance of the class and having the methods -and attributes of that class is sufficient. Sometimes, however, something a -bit more dynamic is called for. In these situations, it's useful to be able -to add extra behaviors on a per-object basis. Perl 6 provides for this by -allowing you to mix a role into an individual object at runtime. - -The example in this chapter uses this in order to allow a bot to gain new -abilities during its lifetime. The C role is at the heart of this. -The first thing to note is the signature of the method C. It -captures the invocant into a variable C<$self>, but also notes that it is -C - that is, something that may be modified. This is because inside the -method, the object itself needs to be modified. Then comes the key part of -this code, and an example of a runtime mix-in. + +=for author + +I don't like the term "mixin" for this. + +=end for + +X + +Class-based OO usually works, where instances have the methods and attributes +of the classes they instantiate. Sometimes, something more dynamic is useful. +Perl 6 allows you to add extra behaviors to individual objects by applying +roles to individual objects at runtime. + +The example in this chapter uses this in order to give bots new abilities +during its lifetime. The C role is at the heart of this. Note the +signature of the method C. It captures the invocant into a variable +C<$self> marked C, which indicates that the invocant may be modified. +Inside the method, that happens: =begin programlisting if %pluggables{$0} -> $plug-in { - $self does $plug-in; + B<$self does $plug-in;> return "Loaded $0"; } =end programlisting -The C<%pluggables> hash maps names of plug-ins to Role objects (like classes, -roles are first-class in Perl 6 and can be passed around just like any other -object). Therefore, the lookup in this hash leaves us with a C in the -variable C<$plug-in>. The C operator is then used to add this role to -C<$self>. From this point on, C<$self> now has all of the methods from the -role, in addition to all of the ones that it had before. Importantly, this -does not have any influence on any other instances of the same class; only -this one instance has changed. +=for author + +The "first-class roles" mention needs to move out of a parenthetical note. + +=end for + +Like classes, roles are first-class in Perl 6; you can pass them around just +like any other object. The C<%pluggables> hash maps names of plug-ins to Role +objects. Thus this lookup stores a Role in C<$plug-in>. The C operator +adds this role to C<$self>--not the I of C<$self>, but the instance +itself. From this point on, C<$self> now has all of the methods from the role, +in addition to all of the ones that it had before. Importantly, this does not +have any influence on any other instances of the same class; only this one +instance has changed. =head2 Differences from compile time composition -The key difference is that methods in the role being mixed in will -automatically override any of the same name within the class of the -object. It's as if you had written an anonymous subclass of the current -class of the object that composed the role into it. This means that C<.*> -will find both those methods that were mixed into the object from a role -(or many roles, if the object is mixed in to many times) along with any -that already existing in the class. - -All of that said, if you wish to mix in multiple roles at a time, you -may do so by putting a list of them on the right hand side of the C -operator. In this case, the roles are not composed one by one, but instead -are composed together into our imaginary anonymous subclass, meaning that -conflicts will be flagged up at this point. This gives a degree of safety, -but since runtime mix-ins happen at runtime, it's never as safe as compile -time composition. Therefore, compile time composition is usually preferable, -if you have to choose. + +Runtime application differs from compile time composition in that methods in +the applied role in will automatically override any of the same name within the +class of the object. It's as if you had written an anonymous subclass of the +current class of the object that composed the role into it. This means that +C<.*> will find both those methods that mixed into the object from one or more +roles along with any that already existed in the class. + +If you wish to apply multiple roles at a time, list them all with C. +This case behaves the same way as compile-time composition, in that the role +composer will compose them all into the imaginary anonymous subclass. As you +might expect, any conflicts will occur at this point. + +This gives a degree of safety, but it happens at runtime and is thus not as +safe as compile time composition. For safety, perform your compositions at +compile time. For example, instead of applying multiple roles to an instance, +compose them into a new role at compile time and apply that role to the +instance. =head2 The C operator