If I have a Role
R
defined as:
role R { method answer { 42 } }
What is the difference (if any) between these two lines:
my $a = 'question' does R;
my $b = 'question' but R;
They appear very similar:
say $a.answer; # OUTPUT: «42»
say $b.answer; # OUTPUT: «42»
say $a.WHAT; # OUTPUT: «(Str+{R})»
say $b.WHAT; # OUTPUT: «(Str+{R})»
Is this a case of there being More Than One Way To Do It™, and these both mean the same thing? Or is there a subtle difference that I'm missing?
note:
I understand that does
is both an operator and a trait and thus can be used when for compile-time mixins (e.g., class C does R {}
) whereas but
is only for runtime mixins. I also understand that but
can be used with an object (e.g., my $c = 'question' but False
) whereas does
can only be used with a Role
. I'm not asking about either of those differences; my only question is about whether there's a difference when both are used at runtime with a Role
. I have read the documentation section on mixing in Role, but didn't see an answer.
===uses the WHICHmethod to obtain the object identity. If you want to create a class that should act as a value type, then that class must create an instance method WHICH, that should return a ValueObjAtobject that won't change for the lifetime of the object. Since Rakudo version 2021.07, ⩶ is an alias for this operator.
Similar to butoperator, the $rolecan instead be an instantiated object, in which case, the operator will create a role for you automatically. The role will contain a single method named the same as $obj.^nameand that returns $obj: my $o = class { method Str { "original" } }.new;
Operator precedence The precedence and associativity of Raku operators determine the order of evaluation of operands in expressions. Where two operators with a different precedence act on the same operand, the subexpression involving the higher-precedence operator is evaluated first.
However, for operators built in to Raku, all operators with the same precedence level also have the same associativity. Setting the associativity of non-infix operators is not yet implemented. In the operator descriptions below, a default associativity of leftis assumed. Operator classification
Put simply:
does
modifies an object in place (and should be used with caution with value types, see note below)
but
returns a new object.
When created off of a literal, it's probably not as evident, but when used with another object, it's pretty clear I think:
role R { method answer { 42 } }
my $question = 'question';
my $but = $question but R;
my $does = $question does R;
say $question.WHAT; # (Str+{R})
say $but.WHAT; # (Str+{R})
say $does.WHAT; # (Str+{R})
say $question.WHERE; # 129371492039210
say $but.WHERE; # 913912490323923
say $does.WHERE; # 129371492039210 <-- same as $question's
Notice I cheated a bit and swapped the order of does
and but
. If I had preserved the order you had, the does
would modify $question
in place, applying the role, meaning that but
would clone $question
(with its role) and apply the role (again!):
my $does = $question does R;
my $but = $question but R;
say $does.WHAT; # (Str+{R})
say $but.WHAT; # (Str+{R}+{R})
This is because does
as an operator is conceptually akin to ++
or +=
, that is, designed to be used in a standalone context, for instance
my $foo = …;
given $bar {
when 'a' { $foo does A }
when 'b' { $foo does B }
when 'c' { $foo does B }
}
Using but
is conceptually closer to using $foo + 1
— mostly meaningless unless assigned to or passed to something else.
does
and value typesIf you use does
on a value type (strings, numbers mainly), there is an extremely high likelihood that you will cause unintended side effects. This is because value types (which, e.g., strings are) are supposed to be immutable and substitutable for one other. Note the following:
role Fooish { }
my $foo = 'foo';
$foo does Fooish;
say 'foo'.WHAT; # (Str+{Fooish})
This is a substitution that's happening at compile time (so it won't affect, e.g, 'foobar'.substr(0,3)
, that happens at runtime), but can cause some truly weird effects if you toss them in a loop:
role Fooish { }
my @a;
@a.push('foo' does Fooish) for ^10;
say @a[0].WHAT; # (Str+{Fooish}+{Fooish}+{Fooish}+{Fooish}+{Fooish}
+{Fooish}+{Fooish}+{Fooish}+{Fooish}+{Fooish})
Applying multiple rolls takes longer and longer the more you do it, so if you change that to ^100000, be ready to wait a while. OTOH, doing but
gives you nice constant time and doesn't pollute the literal. This behavior seems, AFAICT, to be perfectly valid, but definitely something that can catch you unexpectedly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With