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 |

