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.

Calling Via Multiple Dispatch

As we mentioned, multiple dispatch is enabled by agreement of both caller and callee. From the caller's point of view, you invoke multiple dispatch simply by calling with subroutine call syntax instead of method call syntax. It's then up to the dispatcher to figure out which of the arguments are invocants and which ones are just options. (In the case where the innermost visible subroutine is declared non-multi, this degenerates to the Perl 5 semantics of subroutine calls.) This approach lets you refactor a simple subroutine into a more nuanced set of subroutines without changing how the subroutines are called at all. That makes this sort of refactoring drop-dead simple. (Or at least as simple as refactoring ever gets...)

It's a little harder to refactor between single dispatch and multiple dispatch, but a good argument could be made that it should be harder to do that, because you're going to have to think through a lot more things in that case anyway.

Anyway, here's the basic relationship between single dispatch and multiple dispatch. Single dispatch is more familiar, so we'll discuss multiple dispatch first.

Multiple dispatch semantics

Whenever you make a call using subroutine call syntax, it's a candidate for multiple dispatch. A search is made for an appropriate subroutine declaration. As in Perl 5, this search goes outward through the lexical scopes, then through the current package and on to the global namespace (represented in Perl 6 with an initial * for the "wildcard" package name). If the name found is not a multi, then it's a good old-fashioned sub call, and no multiple dispatch is done. End of story.

However, if the first declaration we come to is a multi, then lots of interesting stuff happens. (Fortunately for our performance, most of this interesting stuff can happen at compile time, or upon first use.) The basic idea is that we will collect a complete list of candidates before we decide which one to call.

So the search continues outward, collecting all sub declarations with the same short name but different long names. (We can ignore outer declarations that are hidden by an inner declaration with the same long name.) If we run into a scope with a non-multi declaration, then we're done generating our candidate list, and we can skip the next paragraph.

After going all the way out to the global scope, we then examine the type of the first argument as if we were about to do single dispatch on it. We then visit any classes that would have been single dispatched, in most-derived to least-derived order, and for each of those classes we add into our candidate list any methods declared multi, plus all the single invocant methods, whether or not they were declared multi! In other words, we just add in all the methods declared in the class as a subset of the candidates. (There are reasons for this that we'll discuss below.) Anyway, just as with nested lexical scopes, if two methods have the same long name, the more derived one hides the less derived one. And if there's a class in which the method of the same short name is not declared multi, it serves as a "stopper", just as a non-multi sub does in a lexical scope. (Though that "stopper" method can of course redispatch further up the inheritance tree, just as a "stopper" lexical sub can always call further outward if it wants to.)

Now we have our list of candidates, which may or may not include every sub and method with the same short name, depending on whether we hit a "stopper". Anyway, once we know the candidate list, it is sorted into order of distance from the actual argument types. Any exact match on a parameter type is distance 0. Any miss by a single level of derivation counts as a distance of 1. Any violation of a hard constraint (such as having too many arguments for the number of parameters, or violating a subtype check on a type that does constraint checking, or missing the exact type on a submethod) is effectively an infinite distance, and disqualifies the candidate completely.

Once we have our list of candidates sorted, we simply call the first one on the list, unless there's more than one "first one" on the list, in which case we look to see if one of them is declared to be the default. If so, we call it. If not, we die.

So if there's a tie, the default routine is in charge of subsequent behavior:


    # Pick next best at random...
    multi sub foo (BaseA $a, BaseB $b) is default {
        next METHOD;
    }

    # Give up at first ambiguity...
    multi sub bar (BaseA $a, BaseB $b) is default {
        last METHOD;
    }

    # Invoke my least-derived ancestor
    multi sub baz (BaseA $a, BaseB $b) is default {
        my @ambiguities = WALKMETH($startclass, :method('baz'))
            or last METHOD;
        pop(@ambiguities).($a, $b);
    }

    # Invoke most generic candidate (often a good fall-back)...
    multi sub baz (BaseA $a, BaseB $b) is default {
        my @ambiguities = @CALLER::methods or last METHOD;
        pop(@ambiguities).value.($a, $b);
    }

In many cases, of course, the default routine won't redispatch, but simply do something generically appropriate.

Single dispatch semantics

If you use the dot notation, you are explicitly calling single dispatch. By default, if single dispatch doesn't find a suitable method, it does a "failsoft" to multiple dispatch, pretending that you called a subroutine with the invocant passed as the first argument. (Multiple dispatch doesn't need to failsoft to single dispatch since all single dispatch methods are included as a subset of the multiple dispatch candidates anyway.)

This failsoft behavior can be modified by lexically scoped pragma. If you say


    use dispatch :failhard

then single dispatch will be totally unforgiving as it is in Perl 5. Or you can tell single dispatch to go away:


    use dispatch :multi

in which case all your dot notation is treated as a sub call. That is, any


    $obj.method(1,2,3)

in the lexical scope acts like you'd said:


    method($obj,1,2,3)

If single dispatch locates a class that defines the method, but the method in question turns out to be a set of one or more multi methods, then, the single dispatch fails immediately and a multiple dispatch is done, with the additional constraint that only multis within that class are considered. (If you wanted the first argument to do loose matching as well, you should have called it as a multimethod in the first place.)

Indirect objects

If you use indirect object syntax with an explicit colon, it is exactly equivalent to dot notation in its semantics.

However, one-argument subs are inherently ambiguous, because Perl 6 does not require the colon on indirect objects without arguments. That is, if you say:


    print $fh

it's not clear whether you mean


    $fh.print

or


    print($fh)

As it happens, we've defined the semantics so that it doesn't matter. Since all single invocant methods are included automatically in multimethod dispatch, and since multiple dispatch degenerates to single dispatch when there's only one invocant, it doesn't matter which way your write it. The effect is the same either way. (Unless you've defined your own non-multi print routine in a surrounding lexical scope. But then, if you've done that, you probably did it on purpose precisely because you wanted to disable the default dispatch semantics.)

Meaning of "next METHOD"

Within the context of a multimethod dispatch, "next METHOD" means to try the next best match, if unambiguous, or else the marked default method. From within the default method it means just pick the next in the list even if it's ambiguous. The dispatch list is actually kept in @CALLER::methods, which is a list of pairs, the key of each indicating the "distance" rating, and the value of each containing a reference to the method to call (as a sub ref).

Making Fiends, er, Friends.

If you want to directly access the attributes of a class, your multi must be declared within the scope of that class. Attributes are never directly visible outside a class. This makes it difficult to write an efficient multimethod that knows about the internals of two different classes. However, it's possible for private accessors to be visible outside your class under one condition. If your class declares that another class is trusted, that other class can see the private accessors of your class. If the other class declares that you are trusted, then you can see its private accessor methods. The trust relationship is not necessarily symmetrical. This lets you have an architecture where classes by and large don't trust each other, but they all trust a single well-guarded "multi-plexor" class that keeps everyone else in line.

The syntax for trusting another class is simply:


    class MyClass {
        trusts Yourclass;
        ...
    }

It's not clear whether roles should be allowed to grant trust. In the absence of evidence to the contrary, I'm inclined to say not. We can always relax that later if, after many large, longitudinal, double-blind studies, it turns out to be both safe and effective.

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

Next Pagearrow