Sign In/My Account | View Cart  
advertisement


Listen Print

Apocalypse 12
by Larry Wall | Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Editor's Note: this Apocalypse is out of date and remains here for historic reasons. See Synopsis 12 for the latest information.

Delegation

Delegation is the art of letting someone else do your work for you. The fact that you consider it "your" work implies that delegation is actually a means of taking credit in advance for what someone else is going to do. In terms of objects, it means pretending that some other object's methods are your own. Now, as it happens, you can always do that by hand simply by writing your own methods that call out to another object's methods of the same name. So any shorthand for doing that is pure syntactic sugar. That's what we're talking about here.

Delegation in this sugary sense always requires there to be an attribute to keep a reference to the object we're delegating to. So our syntactic relief will come in the form of annotations on a "has" declaration. We could have decided to instead attach annotations to each method declaration associated with the attribute, but by the time you do this, you've repeated so much information that you almost might as well have written the non-sugary version yourself. I know that for a fact, because that's how I originally proposed it. :-)

Delegation is specified by a "handles" trait verb with an argument specifying one or more method names that the current object and the delegated object will have in common:


    has $:tail handles 'wag';

Since the method name (but nothing else) is known at class construction time, the following .wag method is autogenerated for you:


    method wag (*@args is context(Lazy)) { $:tail.wag(*@args) }

(It's necessary to specify a Lazy context for the arguments to a such a delegator method because the actual signature is supplied by the tail's .wag method, not your method.) So as you can see, the delegation syntax already cuts our typing in half, not to mention the reading. The win is even greater when you specify multiple methods to delegate:


    has $:legs handles «walk run lope shake pee»;

Or equivalently:


    has $:legs handles ['walk', 'run', 'lope', 'shake', 'pee'];

You can also say things like


    my @legmethods := «walk run lope shake pee»;
    has $:legs handles (@legmethods);

since the "has" declaration is evaluated at class construction time.

Of course, it's illegal to call the outer method unless the attribute has been initialized to an object of a type supporting the method. So a declaration that makes a new delegatee at object build time might be specified like this:


    has $:tail handles 'wag' will build { Tail.new(*%_) };

or, equivalently,


    has $:tail handles 'wag' = { Tail.new(*%_) };

This automatically performs


    $:tail = Tail.new(*%_);

when BUILD is called on a new object of the current class (unless BUILD initializes $:tail to some other value). Or, since you might want to declare the type of the attribute without duplicating it in the default value, you can also say


    has Tail $:tail handles 'wag' = { .new(*%_) };

or


    has Tail $:tail handles 'wag' will build { .new(*%_) };

Note that putting a Tail type on the attribute does not necessarily mean that the method is always delegated to the Tail class. The dispatch is still based on the runtime type of the object, not the declared type. So


    has Tail $:tail handles 'wag' = { LongTail.new(*%_) };

delegates to the LongTail class, not the Tail class. Of course, you'll get an exception at build time if you try to say:


    has Tail $:tail handles 'wag' = { Dog.new(*%_) };

since Dog is not derived from Tail (whether or not the tail can wag the dog).

We declare $:tail as a private attribute here, but $.tail would have worked just as well. A Dog's tail does seem to be a public interface, after all. Kind of a read-only accessor.

Wildcard Delegation

We've seen that the argument to "handles" can be a string or a list of strings. But any argument or subargument that is not a string is considered to be a smartmatch selector for methods. So you can say:


    has $:fur handles /^get_/;

and then you can do the .get_wet or .get_fleas methods (presuming there are such), but you can't call the .shake or .roll_in_the_dirt methods. (Obviously you don't want to delegate the .shake method since that means something else when applied to the Dog as a whole.)

If you say


    has $:fur handles Groomable;

then you get only those methods available via the Groomable role or class.

Wildcard matches are evaluated only after it has been determined that there's no exact match to the method name. They therefore function as a kind of autoloading in the overall pecking order. If the class also has an AUTOLOAD, it is called only if none of the wildcard delegations match. (An AUTOMETHDEF is called much earlier, since it knows from the stub declarations whether there is supposed to be a method of that name. So you can think of explicit delegation as a kind of autodefine, and wildcard delegation as a kind of autoload.)

When you have multiple wildcard delegations to different objects, it's possible to have a conflict of method names. Wildcard method matches are evaluated in order, so the earliest one wins. (Non-wildcard method conflicts can be caught at class composition time.)

Renaming Delegated Methods

If, where you would ordinarily specify a string, you put a pair, then the pair maps the method name in this class to the method name in the other class. If you put a hash, each key/value pair is treated as such a mapping. Such mappings are not considered wildcards.


    has $:fur handles { :shakefur«shake» :scratch«get_fleas» };

Perhaps that reads better with the old pair notation:


    has $:fur handles { shakefur => 'shake', scratch => 'get_fleas' };

You can do a wildcard renaming, but not with pairs. Instead do smartmatch with a substitution:


    has $:fur handles (s/^furget_/get_/);

As always, the left-to-right mapping is from this class to the other one. The pattern matching is working on the method name passed to us, and the substituted method name is used on the class we delegate to.

Delegation Without an Attribute

Ordinarily delegation is based on an attribute holding an object reference, but there's no reason in principle why you have to use an attribute. Suppose you had a Dog with two tails. You can delegate based on a method call:


    method select_tail handles «wag hang» {...}

The arguments are sent to both the delegator and delegatee method. So when you call


    $dog.wag(:fast)

you're actually calling


    $dog.select_tail(:fast).wag(:fast)

If you use a wildcard delegation based on a method, you should be aware that it has to call the method before it can even decide whether there's a valid method call to the delegatee or not. So it behooves you not to get too fancy with select_tail(), since it might just have to throw all that work away and go on to the next wildcard specification.

Delegation of Handlers

If your delegation object happens to be an array:


    has @:handlers handles 'foo';

then something cool happens. <cool rays> In this case Perl 6 assumes that your array contains a list of potential handlers, and you just want to call the first one that succeeds. This is not considered a wildcard match unless the "handles" argument forces it to be.

Note that this is different from the semantics of a hyper method such as @objects».foo(), which will try to call the method on every object in @objects. If you want to do that, you'll just have to write your own method:


    has @:ears;
    method twitchears () { @:ears».twitch() }

Life is hard.

Hash-Based Redispatch

If your delegation object happens to be a hash:


    has %:objects handles 'foo';

then the hash provides a mapping from the string value of "self" to the object that should be delegated to:


    has %:barkers handles "bark" =
                (Chihauhau => $yip,
                    Beagle => $yap,
                   Terrier => $arf,
                 StBernard => $woof,
                );
    method prefix:~( return "$.breed" )

If the string is not found in the hash, a "next METHOD" is automatically performed.

Again, this construct is not necessarily considered a wildcard. In the example above we know for a fact that there's supposed to be a .bark method somewhere, therefore a specific method can be autogenerated in the current class.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Next Pagearrow