Documentation for syntax role
assembled from the following pages:
Language documentation: Object orientation §
From Object orientation
(Object orientation) Syntax role role §
Roles are a collection of attributes and methods; however, unlike classes, roles are meant for describing only parts of an object's behavior; this is why, in general, roles are intended to be mixed in classes and objects. In general, classes are meant for managing objects and roles are meant for managing behavior and code reuse within objects.
Roles use the keyword role
preceding the name of the role that is declared. Roles are mixed in using the does
keyword preceding the name of the role that is mixed in.
Roles can also be mixed into a class using is
. However, the semantics of is
with a role are quite different from those offered by does
. With is
, a class is punned from the role, and then inherited from. Thus, there is no flattening composition, and none of the safeties which does
provides.
constant ⲧ = " " xx 4; #Just a ⲧab does Notable my = Journey.new( :origin<Here>, :destination<There>, travelers => <þor Freya> ); .notes("First steps");notes : "Almost there";print ;# OUTPUT: #⤷ Here # First steps # Almost there # #There ⤶
Roles are immutable as soon as the compiler parses the closing curly brace of the role declaration.
Applying roles §
Role application differs significantly from class inheritance. When a role is applied to a class, the methods of that role are copied into the class. If multiple roles are applied to the same class, conflicts (e.g. attributes or non-multi methods of the same name) cause a compile-time error, which can be solved by providing a method of the same name in the class.
This is much safer than multiple inheritance, where conflicts are never detected by the compiler, but are instead resolved to the superclass that appears earlier in the method resolution order, which might not be what the programmer wanted.
For example, if you've discovered an efficient method to ride cows, and are trying to market it as a new form of popular transportation, you might have a class Bull
, for all the bulls you keep around the house, and a class Automobile
, for things that you can drive.
is Bull is Automobile my = Taurus.new;say .steer;# OUTPUT: «Taurus.new(castrated => Bool::True, direction => Any)»
With this setup, your poor customers will find themselves unable to turn their Taurus and you won't be able to make more of your product! In this case, it may have been better to use roles:
role Bull-Like { has Bool $.castrated = False; method steer { # Turn your bull into a steer $!castrated = True; return self; } } role Steerable { has Real $.direction; method steer(Real $d = 0) { $!direction += $d; } } class Taurus does Bull-Like does Steerable { }
This code will die with something like:
===SORRY!=== Method 'steer' must be resolved by class Taurus because it exists in multiple roles (Steerable, Bull-Like)
This check will save you a lot of headaches:
does Bull-Like does Steerable
When a role is applied to a second role, the actual application is delayed until the second role is applied to a class, at which point both roles are applied to the class. Thus
does R1 does R2
produces the same class C
as
does R1 does R2
Stubs §
When a role contains a stubbed method, that is, a method whose code is limited to ...
, a non-stubbed version of a method of the same name must be supplied at the time the role is applied to a class. This allows you to create roles that act as abstract interfaces.
role AbstractSerializable { method serialize() { ... } # literal ... here marks the # method as a stub } # the following is a compile time error, for example # Method 'serialize' must be implemented by Point because # it's required by a role class APoint does AbstractSerializable { has $.x; has $.y; } # this works: class SPoint does AbstractSerializable { has $.x; has $.y; method serialize() { "p($.x, $.y)" } }
The implementation of the stubbed method may also be provided by another role.
Inheritance §
Roles cannot inherit from classes, but they may carry classes, causing any class which does that role to inherit from the carried classes. So if you write:
is Exception does A X::Ouch.^parents.say # OUTPUT: «((Exception))»
then X::Ouch
will inherit directly from Exception, as we can see above by listing its parents.
As they do not use what can properly be called inheritance, roles are not part of the class hierarchy. Roles are listed with the .^roles
metamethod instead, which uses transitive
as flag for including all levels or just the first one. Despite this, a class or instance may still be tested with smartmatches or type constraints to see if it does a role.
does F G.^roles.say; # OUTPUT: «((F))» does Ur does Ar ; Whim.^roles(:!transitive).say; # OUTPUT: «((Ar))» say G ~~ F; # OUTPUT: «True» multi a (F ) multi a () a(G); # OUTPUT: «F»
Pecking order §
A method defined directly in a class will always override definitions from applied roles or from inherited classes. If no such definition exists, methods from roles override methods inherited from classes. This happens both when said class was brought in by a role, and also when said class was inherited directly.
is A does M is A does M B.new.f; # OUTPUT: «I am in class B» C.new.f; # OUTPUT: «I am in role M»
Note that each candidate for a multi-method is its own method. In this case, the above only applies if two such candidates have the same signature. Otherwise, there is no conflict, and the candidate is just added to the multi-method.
Automatic role punning §
Any attempt to directly instantiate a role or use it as a type object will automatically create a class with the same name as the role, making it possible to transparently use a role as if it were a class.
say Point.new(x => 6, y => 8).abs; # OUTPUT: «10» say Point.dimensions; # OUTPUT: «2»
We call this automatic creation of classes punning, and the generated class a pun.
Punning is not caused by most metaprogramming constructs, however, as those are sometimes used to work directly with roles.
Parameterized roles §
Roles can be parameterized, by giving them a signature in square brackets:
[::Type] my = BinaryTree[Int].new-from-list(4, 5, 6);.visit-preorder(); # OUTPUT: «546» .visit-postorder(); # OUTPUT: «465»
Here the signature consists only of a type capture, but any signature will do:
<debug info warn error critical>; [ = ] Logging[].log(debug, 'here we go'); # OUTPUT: «[DEBUG] here we go»
You can have multiple roles of the same name, but with different signatures; the normal rules of multi dispatch apply for choosing multi candidates.
Mixins of roles §
Roles can be mixed into objects. A role's given attributes and methods will be added to the methods and attributes the object already has. Multiple mixins and anonymous roles are supported.
;my = 2 but R;sub f(\bound);f(); # OUTPUT: «hidden!» my := <a b> but R;say .^name; # OUTPUT: «List+{R}»
Note that the object got the role mixed in, not the object's class or the container. Thus, @-sigiled containers will require binding to make the role stick as is shown in the example with @positional
. Some operators will return a new value, which effectively strips the mixin from the result. That is why it might be more clear to mix in the role in the declaration of the variable using does
:
;my does R = <a b>;say .^name; # OUTPUT: «Array+{R}»
The operator infix:<but>
is narrower than the list constructor. When providing a list of roles to mix in, always use parentheses.
my = 1 but R1,R2; # R2 is in sink context, issues a WARNING say .^name;# OUTPUT: «Int+{R1}» my = 1 but (R1,R2);say .^name; # OUTPUT: «Int+{R1,R2}»
If the role supplies exactly one attribute, an initializer can be passed in parentheses:
my = 1.Rat but Named('Remy');say .name; # OUTPUT: «Remy»
Mixins can be used at any point in your object's life.
# A counter for Table of Contents my Num = NaN; # don't do math with Not A Number say ; # OUTPUT: «NaN» does TOC-Counter; # now we mix the role in .inc(1).inc(2).inc(2).inc(1).inc(2).inc(2).inc(3).inc(3);put / 1; # OUTPUT: «NaN» (because that's numerical context) put ; # OUTPUT: «2.2.2» (put will call TOC-Counter::Str)
Roles can be anonymous.
my of Int is default(0 but role :: );say <not-there>; # OUTPUT: «NULL» say <not-there>.defined; # OUTPUT: «True» (0 may be False but is well defined) say Int.new(<not-there>); # OUTPUT: «0»
Language documentation: Glossary §
From Glossary
(Glossary) Reference role role §
Roles, mix-ins or traits define interfaces and/or implementation of those interfaces as well as instance variables using them, and are mixed-in when declaring classes that follow that interface. Abstract classes are particular examples of Roles where the actual implementation is deferred to the class that uses that Role.
Roles are part of Raku's object system, and are declared using the role keyword and used in class declaration via does.