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.

Eliminating Redundancy in Constructor Calls

If you say:


    my Dog $spot = Dog.new(...)

you have to repeat the type. That's not a big deal for a small typename, but sometime typenames are a lot longer. Plus you'd like to get rid of the redundancy, just because it's, like, redundant. So there's a variant on the dot operator that looks a lot like a dot assignment operator:


    my Dog $spot .= new(...)

It doesn't really quite fit the assignment operator rule though. If it did, it'd have to mean:


    my Dog $spot = $spot.new(...)

which doesn't quite work, because $spot is undefined. What probably happens is that the my cheats and puts a version of undef in there that knows it should dispatch to the Dog class if you call .self:new() on it. Anyway, we'll make it work one way or another, so that it becomes the equivalent of:


    my Dog $spot = Dog.new(...)

The alternative is to go the C++ route and make new a reserved word. We're just not going to do that.

Note that an attribute declaration of the form


    has Tail $wagger .= new(...)

might not do what you want done when you want it done, if what you want done is to create a new Dog object each time an object is built. For that you'd have to say:


    has Tail $wagger = { .new(...) }

or equivalently,


    has Tail $wagger will build { .new(...) }

But leaving aside such timing issues, you should generally think of the .= operator more as a variant on . than a variant on +=. It can, for instance, turn any non-mutating method call into a mutating method:


    @array.=sort;       # sort @array in place
    .=lc;               # lowercase $_ in place

This presumes, of course, that the method's invocant and return value are of compatible types. Some classes will wish to define special in-place mutators. The syntax for that is:


    method self:sort (Array @a is rw) {...}

It is illegal to use return from such a routine, since the invocant is automatically returned. If you do not declare the invocant, the default invocant is automatically considered "rw". If you do not supply a mutating version, one is autogenerated for you based on the corresponding copy operator.

Object Deconstruction

Object destruction is no longer guaranteed to be "timely" in Perl 6. It happens when the garbage collector gets around to it. (Though there will be ways to emulate Perl 5 end-of-scope cleanup.)

As with object creation, object destruction is recursive. Unlike creation, it must proceed in the opposite order.

DESTROYALL

The DESTROYALL routine is the counterpart to the BUILDALL routine. Similarly, the default definition is normally sufficient for the needs of most classes. DESTROYALL first calls DESTROY on the current class, and then delegates to the DESTROYALL of any parent classes. In this way the pieces of the object are disassembled in the correct order, from most derived to least derived.

DESTROY

As with Perl 5, all the memory deallocation is done for you, so you really only need to define DESTROY if you have to release external resources such as files.

Since DESTROY is the opposite of BUILD, if any attribute declaration has a "destroy" property, that property (presumably a closure) is evaluated before the main block of DESTROY. This happens even if you don't declare a DESTROY.

(The "build" and "destroy" traits are the only way for roles to let their preferences be made known at BUILD and DESTROY time. It follows that any role that does not define an attribute cannot participate in building and destroying except by defining a method that BUILD or DESTROY might call. In other words, stateless roles aren't allowed to muck around with the object's state. This is construed as a feature.)

Dispatch Mechanisms

Perl 6 supports both single dispatch (traditional OO) and multiple dispatch (also known as "multimethod dispatch", but we try to avoid that term).

Single Dispatch

Single dispatch looks up which method to run solely on the basis of the type of the first argument, the invocant. A single-dispatch call distinguishes the invocant syntactically (unlike a multiple-dispatch call, which looks like a subroutine call, or even an operator.)

Basically, anything can be an invocant as long as it fills the Dispatch role, which provides a .dispatcher method. This includes ordinary objects, class objects, and (in some cases) even varieties of undef that happen to know what class of thing they aren't (yet).

Simple single dispatch is specified with the dot operator, or its indirect object equivalent:


    $object.meth(@args)   # always calls public .meth
           .meth(@args)   # unary form
    meth $object: @args   # indirect object form

There are variants on the dot form indicated by the character after the dot. (None of these variants allows indirect object syntax.) The private dispatcher only ever dispatches to the current class or its proxies, so it's really more like a subroutine call in disguise:


    $object.:meth(@args)  # always calls private :meth
           .:meth(@args)  # unary form

It is an error to use .: unless there is a correspondingly named "colon" method in the appropriate class, just as it is an error to use . when no method can be found of that name. Unlike the .: operator, which can have only one candidate method, the . operator potentially generates a list of candidates, and allows methods in that candidate list to defer to subsequent methods in other classes until a candidate has been found that is willing to handle the dispatch.

In addition to the .: and .= operators, there are three other dot variants that can be used if it's not known how many methods are willing to handle the dispatch:


    $object.?meth(@args)  # calls method if there is one
           .?meth(@args)  # unary form
    $object.*meth(@args)  # calls all methods (0 or more)
           .*meth(@args)  # unary form
    $object.+meth(@args)  # calls all methods (1 or more)
           .+meth(@args)  # unary form

The .* and .+ versions are generally only useful for calling submethods, or methods that are otherwise expected to work like submethods. They return a list of all the successful return values. The .? operator either returns the one successful result, or undef if no appropriate method is found. Like the corresponding regex modifiers, ? means "0 or 1", while * means "0 or more", and + means "1 or more". Ordinary . means "exactly one". Here are some sample implementations, though of course these are probably implemented in C for maximum efficiency:


    # Implements . (or .? if :maybe is set).
    sub CALLONE ($obj, $methname, +$maybe, *%opt, *@args) {
        my $startclass = $obj.dispatcher() // fail "No dispatcher: $obj";
      METHOD:
        for WALKMETH($startclass, :method($methname), %opt) -> &meth {
            return meth($obj, @args);
        }
        fail qq(Can't locate method "$methname" via class "$startclass")
            unless $maybe;
        return;
    }

With this dispatcher you can continue by saying "next METHOD". This allows methods to "failover" to other methods if they choose not to handle the request themselves.


    # Implements .+ (or .* if :maybe is set).
    #   Add :force to redispatch in every class
    sub CALLALL ($obj, $methname, +$maybe, +$force, *%opt, *@args) {
        my $startclass = $obj.dispatcher() // fail "No dispatcher: $obj";
        my @results = gather {
            if $force {
              METHOD:
                for WALKCLASS($startclass, %opt) -> $class {
                    take $obj.::($class)::$methname(*@args) # redispatch
                }
            }
            else {
              METHOD:
                for WALKMETH($startclass, :method($methname), %opt) -> &meth {
                    take meth($obj,*@args);
                }
            }
        }
        return @results if @results or $maybe;
        fail qq(Can't locate method "$methname" via class "$startclass");
    }

This one you can quit early by saying "last METHOD". Notice that both of these dispatchers cheat by calling a method as if it were a sub. You may only do that by taking a reference to the method, and calling it as a subroutine, passing the object as the first argument. This is the only way to call a virtual method non-virtually in Perl. If you try to call a method directly as a subroutine, Perl will ignore the method, look for a subroutine of that name elsewhere, probably not find it, and complain bitterly. (Or find the wrong subroutine, and execute it, after which you will complain bitterly.)

We snuck in an example the new gather/take construct. It is still somewhat conjectural.

Calling Superclasses, and Not-So-Superclasses

Perl 5 supplies a pseudoclass, SUPER::, that redirects dispatch to a parent class's method. That's often the wrong thing to do, though, in part because under MI you may have more than one parent class, and also because you might have sibling classes that also need to have the given method triggered. Even if SUPER is smart enough to visit multiple parent classes, and even if all your classes cooperate and call SUPER at the right time, the depth first order of visitation might be the wrong order, especially under diamond inheritance. Still, if you know that your parent classes use SUPER, or you're calling into a language with SUPER semantics (such as Perl 5) then you should probably use SUPER semantics too, or you'll end up calling your parent's parents in duplicate. However, since use of SUPER is slightly discouraged, we Huffman code it a bit longer in Perl 6. Remember the *%opt parameters to the dispatchers above? That comes in as a parameterized pseudoclass called WALK.


    $obj.*WALK[:super]::method(@args)

That limits the call to only those immediate super classes that define the method. Note the star in the example. If you really want the Perl 5 semantics, leave the star out, and you'll only get the first existing parent method of that name. (Why you'd want that is beyond me.)

Actually, we'll probably still allow SUPER:: as a shorthand for WALK[:super]::, since people will just hack it in anyway if we don't provide it...

If you think about it, every ordinary dispatch has an implicit WALK modifier on the front that just happens to default to WALK[:canonical]. That is, the dispatcher looks for methods in the canonical order. But you could say WALK[:depth] to get Perl 5's order, or you could say WALK[:descendant] to get an order approximating the order of construction, or WALK[:ascendant] to get an order approximating the order of destruction. You could say WALK[:omit(SomeClass)] to call all classes not equivalent to or derived from SomeClass. For instance, to call all super classes, and not just your immediate parents, you could say WALK[:omit(::_)] to skip the current lexical class or anything derived from it.

But again, that's not usually the right thing to do. If your base classes are all willing to cooperate, it's much better to simply call


    $obj.method(@args)

and then let each of the implementations of the method defer to the next one when they're done with their part of it. If any method says "next METHOD", it automatically iterates the loop of the dispatcher and finds the next method to dispatch to, even if that method comes from a sibling class rather than a parent class. The next method is called with the same arguments as originally supplied.

That presupposes that the entire set of methods knows to call "next" appropriately. This is not always the case. In fact, if they don't all call next, it's likely that none of them does. And maybe just knowing whether or not they do is considered a violation of encapsulation. In any case, if you still want to call all the methods without their active cooperation, then use the star form:


    $obj.*method(@args)

Then the various methods don't have to do anything to call the next method--it happens automatically by default. In this case a method has to do something special if it wants to stop the dispatch. Naturally, that something is to call "last METHOD", which terminates the dispatch loop early.

Now, sometimes you want to call the next method, but you want to change the arguments so that the next method doesn't get the original argument list. This is done with deep magic. If you use the call keyword in an ordinary (nonwrapper) method, it steals the rest of the dispatch list from the outer loop and redispatches to the next method with the new arguments:


    @retvals = call(@newargs)
    return @retvals;

And unlike with "next METHOD", control returns to this method following the call. It returns the results of the subsequent method calls, which you should return so that your outer dispatcher can add them to the return values it already gathered.

Note that "next METHOD" and "last METHOD" can typically be spelt "next" and "last" unless they are in an inner loop.

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

Next Pagearrow