=encoding utf8
=head1 TITLE
Synopsis 14: Roles and Parametric Types [DRAFT]
=head1 VERSION
Created: 24 Feb 2009 (extracted from S12-objects.pod)
Last Modified: 24 Nov 2012
Version: 11
=head1 Overview
This synopsis discusses roles and parametric types, which were
originally discussed in A12.
=head1 Roles
Classes are primarily in charge of object management, and only
secondarily in charge of software reuse. In Perl 6, roles take over
the job of managing software reuse. Depending on how you care to look
at it, a role is like a partial class, or an interface with default
implementation, or a set of generic methods and their associated data,
or a class closed at compile time.
Roles may be composed into a class at compile time, in which case
you get automatic detection of conflicting methods. A role may also
be mixed into a class or object at run time to produce an anonymous
derived class with extra capabilities, but in this case conflicting
methods are overridden by the new role silently. In either case,
a class is necessary for instantiation--a role may not be directly
instantiated.
A role is declared like a class, but with a C keyword:
role Pet {
method feed ($food) {
$food.open_can;
$food.put_in_bowl;
self.eat($food);
}
}
A role may not inherit from a class, but may be composed of other
roles. However, this "crony" composition is not evaluated until
class composition time. This means that if two roles bring in the
same crony, there's no conflict--it's just as if the class pulled in
the crony role itself and the respective roles didn't. A role may
never conflict with itself regardless of its method of incorporation.
A role that brings in two conflicting crony roles I resolve them
as if it were a class. This solution is accepted by the class unless
the class supplies its own solution. If two different roles resolve
the same crony conflict two different ways, those roles are themselves
in conflict and must be resolved by a "more derived" role or the class.
A role doesn't know its own type until it is composed into a class.
Any mention of its main type (such as C<::?CLASS>) is generic, as is
any reference to C or the type of the invocant. You can use
a role name as a type, but only for constraints, not for declaring
actual objects. (However, if you use a role as if it were a class,
an anonymous class is generated that composes the role, which provides
a way to force a role to test its crony composition for infelicities.)
If a role merely declares methods without defining them, it degenerates
to an interface:
role Pet {
method feed ($food) {...}
method groom () {...}
method scratch (:$where) {...}
}
Note that, while these methods must become available at class
composition time, they might be supplied by any of: another role,
the class itself, or some superclass. We know the methods that are
coming from the other roles or the class, but we don't necessarily know
the complete set of methods supplied by our super classes if they are
open or rely on wildcard delegation. However, the class composer is
allowed to assume that only currently declared superclass methods or
non-wildcard methods are going to be available. A stub can always
be installed somewhere to "supply" a missing method's declaration.
Roles may have attributes:
role Pet {
has $.collar = Collar.new(tag => Tag.new);
method id () { return $.collar.tag }
method lose_collar () { undefine $.collar }
}
Within a role the C declarator always indicates the declaration
from the viewpoint of the class. Therefore a private attribute declared
using C is private to the class, not to the role. You may wish to
declare an attribute that is hidden even from the class; a completely
private role attribute (that will exist per instance of the class) may
be declared like this:
my $!spleen;
The name of such a private attribute is always considered lexically scoped.
If a role declares private lexical items, those items are private to
the role due to the nature of lexical scoping. Accessors to such
items may be exported to the class, but this is not the default.
In particular, a role may say
trusts ::?Class;
to allow C access to the role's C<$!attr> variables with the
class or from other roles composed into the class. Conflicts between
private accessors are also caught at composition time, but of course
need not consider super classes, since no-one outside the current
class (or a trusted class) can call a private accessor at all.
(Private accessors are never virtual, and must be package qualified
if called from a trusted scope other than our own. That is, it's
either C or C<$obj!TrustsMe::attr().>)
A role may also distinguish a shared method
has method foo ...
method foo ... # same
from a nonshared private method:
my method !foo ...
my method foo ... # same, but &foo is aliased to &!foo
Generally you'd just use a lexically scoped sub, though.
my sub foo ...
A role can abstract the decision to delegate:
role Pet {
has $groomer handles = hire_groomer();
}
Note that this puts the three methods into the class as well as
C<$groomer>. In contrast, "C" would only put the
three methods; the attribute itself is private to the role.
A role is allowed to declare an additional inheritance for its class when
that is considered an implementation detail:
role Pet {
also is Friend;
}
=head2 Compile-time Composition
A class incorporates a role with the verb "does", like this:
class Dog is Mammal does Pet does Sentry {...}
or equivalently, within the body of the class closure:
class Dog {
also is Mammal;
also does Pet;
also does Sentry;
...
}
or
class Dog {
also is Mammal does Pet does Sentry;
...
}
There is no ordering dependency among the roles.
A class's explicit method definition hides any role definition of
the same name. A role method in turn hides any methods inherited
from other classes.
If there are no method name conflicts between roles (or with the
class), then each role's methods can be installed in the class. If,
however, two roles try to introduce a method of the same name the
composition of the class fails. (Two C attributes of the same
name, whether public or private, are always a composition fail.
Role-private attributes are exempt from this, and from the viewpoint of
the composition, don't even exist, except to allocate a slot for each
such attribute.)
There are several ways to solve method conflicts. The first is simply to
write a class method that overrides the conflicting role methods, perhaps
figuring out which role method to call.
Alternately, if the role's methods are declared C, they can be
disambiguated based on their long name. If the roles forget to declare
them as multi, you can force a multi on the roles' methods by installing
a proto stub in the class being constructed:
proto method shake {...}
(This declaration need not precede the C clause textually, since
roles are not actually composed until the end of the class definition,
at which point we know which roles are to be composed together
in a single logical operation, as well as how the class intends to
override the roles.)
The proto method will be called if the multi fails:
proto method shake { warn "They couldn't decide" }
=head2 Run-time Mixins
Run-time mixins are done with C and C. The C binary
operator is a mutator that derives a new anonymous class (if necessary)
and binds the object to it:
$fido does Sentry
The C infix operator is non-associative, so this is a syntax error:
$fido does Sentry does Tricks does TailChasing does Scratch;
You can, however, say
$fido does Sentry;
$fido does Tricks;
$fido does TailChasing;
$fido does Scratch;
And since it returns the left side, you can also say:
((($fido does Sentry) does Tricks) does TailChasing) does Scratch;
Unlike the compile-time role composition, each of these layers on a new
mixin with a new level of inheritance, creating a new anonymous class
for dear old Fido, so that a C<.chase> method from C hides a
C<.chase> method from C.
You can also mixin a precomposed set of roles:
$fido does (Sentry, Tricks, TailChasing, Scratch);
This will level the playing field for collisions among the new
set of roles, and guarantees the creation of no more than one more
anonymous class. Such a role still can't conflict with itself, but it
can hide its previous methods in the parent class, and the calculation
of what conflicts is done again for the set of roles being mixed in.
If you can't do compile-time composition, we strongly recommend this
approach for run-time mixins since it approximates a compile-time
composition at least for the new roles involved.
A role applied with C may be parameterized with an initializer
in parentheses, but only if the role supplies exactly one attribute
to the mixin class:
$fido does Wag($tail);
$line does taint($istainted);
Note that the parenthesized form is I a subroutine or method call.
It's just special initializing syntax for roles that contain a single
property.
The supplied initializer will be coerced to the type of the attribute.
Note that this initializer is in addition to any parametric type
supplied in square brackets, which is considered part of the actual
type name:
$myobj does Array[Int](@initial)
A property is defined by a role like this:
role answer {
has Int $.answer is rw = 1;
}
The property can then be mixed in or, alternatively, applied using the
C operator. C is like C, but creates a copy and mixes into
that instead, leaving the original unmodified. Thus:
$a = 0 but answer(42)
Really means something like:
$a = ($anonymous = 0) does answer(42);
Which really means:
(($anonymous = 0) does answer).answer = 42;
$a = $anonymous;
Which is why there's a C operator.
If you put something that is not a role on the right hand side of the
C or C operators then an anonymous role will be auto-generated
containing a single method that returns that value. The name of the method
is determined by doing .WHAT.perl on the value supplied on the RHS. The
generated role is then mixed in to the object. For example:
$x does 42
Is equivalent to:
$x does role { method Int() { return 42 } }
Note that the role has no attributes and thus no storage; if you want that,
then you should instead use:
$x does Int(42)
Which mixes in the Int role and initializes the single storage location Int
that it declares with 42, and provides an lvalue accessor.
Note that .WHAT on an enumeration value stringifies to the name of the
enumeration, and as a result:
0 but True
Is equivalent to:
0 but role { method Bool() { return True } }
And thus the resulting value will be considered true in boolean context.
The list syntax for composing multiple roles in a single C or C
by putting them in a list also applies here. Thus:
42 but ("the answer", False)
Is equivalent to:
42 but (role { method Str() { return "the answer" } },
role { method Bool() { return False } })
Which gives you a compact way to build context-sensitive return values.
Note that multiple roles rather than a single one are generated, so that
anything like:
42 but (True, False)
Will fail as a result of standard role composition semantics (because two
roles are both trying to provide a method Bool).
=head1 Traits
Traits are just properties (roles) applied to something that is being declared (the I),
such as containers or classes. It's the declaration of the item itself that
makes traits seem more permanent than ordinary properties. In addition
to adding the property, a trait can also have side effects.
Traits are generally applied with the "is" keyword, though not always.
To define a trait handler for an "is xxx" trait, define one or
more multisubs into a property role like this:
role xxx {
has Int $.xxx;
multi trait_mod:(::?CLASS:U $declarand, :$xxx!) {...}
multi trait_mod:(Any $declarand, :$xxx!) {...}
}
Then it can function as a trait. A well-behaved trait handler will say
$declarand does xxx($arg);
somewhere inside to set the metadata on the declarand correctly.
Since a class can function as a role when it comes to parameter type
matching, you can also say:
class MyBase {
multi trait_mod:(MyBase:U $declarand, MyBase $base) {...}
multi trait_mod:(Any $declarand, MyBase $tied) {...}
}
These capture control if C wants to capture control of how it gets
used by any class or container. But usually you can just let it call
the generic defaults:
multi trait_mod:(Any:U $declarand, $base) {...}
which adds C<$base> to the "isa" list of class C<$declarand>, or
multi trait_mod:(Any $declarand, $tied) {...}
which sets the "tie" type of the container declarand to the implementation type
in C<$tied>.
Most traits are really just adverbial pairs which, instead of being
introduced by a colon, are introduced by a (hopefully) more readable
"helping verb", which could be something like "C", or "C", or
"C", or "C", or "C", or "C". Any trait verb
that is parsed the same as trait_mod: may be defined the same way.
Here's "C", which (being syntactic sugar) merely delegates to
back to "is":
multi sub trait_mod:($declarand, :$trait) {
trait_mod:($declarand, :$trait);
}
Other traits are applied with a single word, and require special
parsing. For instance, the "C" trait is defined something
like this:
role of {
has ReturnType $.of;
multi sub trait_mod:($declarand, ReturnType $arg) is parsed // {
$declarand does of($arg);
}
...
}
Unlike compile-time roles, which all flatten out in the same class,
compile-time traits are applied one at a time, like mixin roles.
You can, in fact, apply a trait to an object at run time, but
if you do, it's just an ordinary mixin role. You have to call the
appropriate C()> routine yourself if you want it to
do any extra shenanigans. The compiler won't call it for you at run
time like it would at compile time.
Note that the declarations above are insufficient to install new trait
auxiliaries or verbs into the user's grammar, since macro definitions
are lexically scoped, and in the declarations above extend only to
the end of the role definition. The user's lexical scope must somehow
have processed (or imported) a proto declaration introducing the new
syntax before it can be parsed correctly. (This doesn't apply to
pre-existing syntax such as C, of course.)
Calls to C routines are evaluated in sink context.
=head1 Parametric Roles
A role's main type is generic by default, but you can also parameterize
other types explicitly using type parameters:
role Pet[::Petfood = TableScraps] {
method feed (Petfood $food) {...}
}
(Note that in this case you must not use ::Petfood in the inner declaration,
or it would rebind the type to type of the actual food parameter.)
If you want to parameterize the initial value of a role attribute,
be sure to put a double semicolon if you don't want the parameter to
be considered part of the long name:
role Pet[::ID;; $tag] {
has ID $.collar .= new($tag);
}
You don't just have to parameterize on types; any value is fine. Imagine
we wanted to factor out a "greet" method into a role, which takes
somebody's name and greets them. We can parameterize it on the greeting.
role Greet[Str $greeting] {
method greet() { say "$greeting!"; }
}
class EnglishMan does Greet["Hello"] { }
class Slovak does Greet["Ahoj"] { }
class Lolcat does Greet["OH HAI"] { }
EnglishMan.new.greet(); # Hello
Slovak.new.greet(); # Ahoj
Lolcat.new.greet(); # OH HAI
Similarly, we could do a role for requests.
role Request[Str $statement] {
method request($object) { say "$statement $object?"; }
}
class EnglishMan does Request["Please can I have a"] { }
class Slovak does Request["Prosim si"] { }
class Lolcat does Request["I CAN HAZ"] { }
EnglishMan.new.request("yorkshire pudding");
Slovak.new.request("borovicka");
Lolcat.new.request("CHEEZEBURGER");
Sadly, the Slovak output sucks here. Borovicka is the nominative form
of the word, and we need to decline it into the accusative case. But
some languages don't care about that, and we don't want to have to make
them all supply a transform. Thankfully, you can write many roles with
the same short name, and a different signature, and multi-dispatch will
pick the right one for you (it is the exact same dispatch algorithm used
by multi-subs). So we can write:
role Request[Str $statement] {
method request($object) { say "$statement $object?"; }
}
role Request[Str $statement, &transform] {
method request($object) {
say "$statement " ~ transform($object) ~ "?";
}
}
module Language::Slovak {
sub accusative($nom) {
# ...and before some smartass points it out, I know
# I'm missing some of the masculine animate declension...
return $nom.subst(/a$/, 'u');
}
}
class EnglishMan does Request["Please can I have a"] { }
class Slovak does Request["Prosim si", &Language::Slovak::accusative] { }
class Lolcat does Request["I CAN HAZ"] { }
EnglishMan.new.request("yorkshire pudding");
Slovak.new.request("borovicka");
Lolcat.new.request("CHEEZEBURGER");
Which means we can now properly order our borovicka in Slovakia, which
is awesome. Until you do it in a loop and find the Headache['very bad']
role got mixed into yourself overnight, anyway...
=head2 Relationship Between of And Types
The of keyword is just syntactic sugar for providing a single
parameter to a parametric type. Thus:
my Array of Recipe %book;
Actually means:
my Array[Recipe] %book;
This can be nested, so:
my Hash of Array of Recipe @library;
Is just:
my Hash[Array[Recipe]] @library;
Therefore:
my Array @array;
Means an Array of Array (actually, a Positional of Array).
=head2 Parametric Subtyping
If you have two types in a subtyping relationship such that T1 is
narrower than T2, then also the roles:
role R[::T] { }
role R[::T1, ::T2] { }
Will act such that R[T1] is narrower than R[T2]. This extends to multiple
parameters, however they must all be narrower or the same (this is unlike
in multiple dispatch where you can have one narrower and the rest narrower
or tied). That is, assuming we have some unrelated type T3, then R[T2, T1]
is narrower than R[T1,T1] but R[T2,T1] is not narrower than R[T1,T3].
Nesting follows naturally from this definition, so a role R[R[T2]] is
narrower than a role R[R[T1]].
This all means that, for example, if you have a sub:
sub f(Num @arr) { ... }
Then you can also call it with an array of Int.
my Int @a = 1,2,3;
f(@a);
=head2 The structure of role types and packages
When a role is declared:
role Foo { }
Then the symbol `Foo` that gets installed in the enclosing package represents
a potential group of roles that will be disambiguated by arguments. A later
declaration:
role Foo[::T] { }
Adds to the existing group. The C<::?PACKAGE> inside of each of these is
therefore something different from Foo itself. The group's meta-object type
is C, while C is the meta-object
type for individual parametric roles.
Writing C is not sufficient to concretize a role, because that
needs the target class; therefore this returns a type with the meta-object
C that will pass along the extra type parameters at the
point of composition, to aid selection of the correct parametric variant.
Therefore, it does not address a particular C.
Normal users of Perl 6 will have to care little for these details. However,
given that any C scoped symbol declared inside of a role body will end
up in a package that is simply unaddressable without digging into the
meta-objects, C declarations in a role are forbidden. This does not
prevent installation directly into C<::?PACKAGE.WHO>, for expert users who
want the escape hatch. It simply means syntactic C declarations will be
forbidden to avoid the inevitable confusion that will result from trying to
access them.
=head2 Interaction of typed and untyped data structures
Certainly so far as Perl 6.0.0 goes, only types that have been declared
on a container count in the type check. That is, if we have a sub:
sub f(Int @arr) { ... }
And call it with any of:
f([1,2,3]);
my @a = 1,2,3;
f(@a);
Then neither of these calls will work. The type check is based on the
declared type of the array, and the content is unknown to the type
checker.
=head1 AUTHORS
Larry Wall
Tim Nelson
Jonathan Worthington
=for vim:set expandtab sw=4: