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.
Overloading
In Perl 5 overloading was this big special deal that had to have special hooks inserted all over the C code to catch various operations on overloaded types and do something special with them. In Perl 6, that just all falls out naturally from multiple dispatch. The only other part of the trick is to consider operators to be function calls in disguise. So in Perl 6 the real name of an operator is composed of a grammatical context identifier, a colon, and then the name of the operator as you usually see it. The common context identifiers are "prefix", "infix", "postfix", "circumfix", and "term", but there are others.
So when you say something like
$x = <$a++ * -@b.[...]>;
you're really saying something like this:
$x = circumfix:<>(
infix:*(
postfix:++($a),
prefix:-(
infix:.(
@b,
circumfix:[](
term:...();
)
)
)
)
)
Perl 5 had special key names representing stringification and numification. In Perl 6 these naturally fall out if you define:
method prefix:+ () {...} # what we do in numeric context
method prefix:~ () {...} # what we do in string context
Likewise you can define what to return in boolean context:
method prefix:? () {...} # what we do in boolean context
Integer context is, of course, just an ordinary method:
method int () {...} # what we do in integer context
These can be defined as normal methods since single-invocant multi subs degenerate to standard methods anyway. C++ programmers will tend to feel comfy defining these as methods. But others may prefer to declare them as multi subs for consistency with binary operators. In which case they'd look more like this:
multi sub *prefix:+ (Us $us) {...} # what we do in numeric context
multi sub *prefix:~ (Us $us) {...} # what we do in string context
multi sub *prefix:? (Us $us) {...} # what we do in string context
multi sub *prefix:int (Us $us) {...} # what we do in integer context
Coercions to other classes can also be defined:
multi sub *coerce:as (Us $us, Them ::to) { to.transmogrify($us) }
Such coercions allow both explicit conversion:
$them = $us as Them;
as well as implicit conversions:
my Them $them = $us;
Binary Ops
Binary operators should generally be defined as multi subs:
multi sub infix:+ (Us $us, Us $ustoo) {...}
multi sub infix:+ (Us $us, Them $them) is commutative {...}
The "is commutative" trait installs an additional autogenerated
sub with the invocant arguments reversed, but with the same semantics
otherwise. So the declaration above effectively autogenerates this:
multi sub infix:+ (Them $them, Us $us) {...}
Of course, there's no need for that if the two arguments have the same type. And there might not actually be an autogenerated other subroutine in any case, if the implementation can be smart enough to simply swap the two arguments when it needs to. However it gets implemented, note that there's no need for Perl 5's "reversed arguments flag" kludge, since we reverse the parameter name bindings along with the types. Perl 5 couldn't do that because it had no control of the signature from the compiler's point of view.
See Apocalypse 6 for much more on the definition of user-defined operators, their precedence, and their associativity. Some of it might even still be accurate.
Class Composition with Roles
Objects have many kinds of relationships with other objects. One of the pitfalls of the early OO movement was to encourage people to model many relationships with inheritance that weren't really "isa" relationships. Various languages have sought to redress this deficiency in various ways, with varying degrees of success. With Perl 6 we'd like to back off a step and allow the user to define abstract relationships between classes without committing to a particular implementation.
More specifically, we buy the argument of the Traits paper that classes should not be used both to manage objects and to manage code reuse. It needs to be possible to separate those concerns. Since a lot of the code that people want to reuse is that which manages non-isa object relationships, that's what we should abstract out from classes.
That abstraction we are calling a role. Roles can encompass both interface and implementation of object relationships. A role without implementation degenerates to an interface. A role without interface degenerates to privately instantiated generics. But the typical role will provide both interface and at least a default implementation.
Unlike the Traits paper, we will allow state as part of our implementation. This is necessary if we are to abstract out the delegation decision. We feel that the decision to delegate rather than compose a sub-object is a matter of implementation, and therefore that decision should be encapsulated (or at least be allowed to be encapsulated) in a role. This allows you to refactor a problem by redefining one or more roles without having to doctor all the classes that make use of those roles. This is a great way to turn your huge, glorious "god object" into a cooperating set of objects that know how to delegate to each other.
As in the Traits paper, roles are composed at class construction time, and the class composer does some work to make sure the composed class is not unintentionally ambiguous. If two methods of the same name are composed into the same class, the ambiguity will be caught. The author of the class has various remedies for dealing with this situation, which we'll go into below.
From the standpoint of the typical user, a role just looks like a "smart" include of a "partial class". They're smart in that roles have to be well behaved in certain respects, but most of the time the naive user can ignore the power of the abstraction.
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 |

