将 Role 混合到 Raku 中的对象时,`does` 与 `but` 运算符

`does` versus `but` operators when mixing in a Role into an object in Raku

如果我有一个 Role R 定义为:

role R { method answer { 42 } }

这两行之间有什么区别(如果有的话):

my $a = 'question' does R;
my $b = 'question' but  R;

它们看起来非常相似:

say $a.answer;  # OUTPUT: «42»
say $b.answer;  # OUTPUT: «42»

say $a.WHAT;    # OUTPUT: «(Str+{R})»
say $b.WHAT;    # OUTPUT: «(Str+{R})»

这是不是存在不止一种方法™,而且这两个意思是一样的?还是我遗漏了细微的差别?

:
我知道 doesboth 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,但没有看到答案。

简单地说:

  • does 就地修改一个对象(应该谨慎使用值类型,见下面的注释)

  • but returns 一个新对象。

当从文字创建时,它可能不那么明显,但当与另一个对象一起使用时,我认为它很清楚:

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

注意我作弊了一点,调换了 doesbut 的顺序。如果我保留了你的顺序,does 将修改 $question,应用角色,这意味着 but 将克隆 $question(及其角色)并应用角色(再次!):

my $does = $question does R;
my $but  = $question but  R;

say $does.WHAT;  # (Str+{R})
say $but.WHAT;   # (Str+{R}+{R})

这是因为 does 作为运算符在概念上类似于 +++=,也就是说,设计用于独立上下文,例如

my $foo = …;
given $bar {
   when 'a' { $foo does A }
   when 'b' { $foo does B }
   when 'c' { $foo does B }
}

使用 but 在概念上更接近于使用 $foo + 1 — 除非分配给或传递给其他东西,否则几乎没有意义。

does 和值类型

的警告

如果您在 值类型 (主要是字符串、数字)上使用 does,您极有可能会导致意外的副作用。这是因为值类型(例如,字符串)应该是不可变的并且可以相互替代。请注意以下几点:

role Fooish { }

my $foo = 'foo';
$foo does Fooish;

say 'foo'.WHAT; # (Str+{Fooish})

这是在编译时发生的替换(因此它不会影响,例如,在运行时发生的 'foobar'.substr(0,3)),但如果将它们放入循环中,可能会导致一些非常奇怪的效果:

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})

应用多次滚动需要的时间越来越长,因此如果将其更改为 ^100000,请准备好等待一段时间。 OTOH,做 but 给你很好的恒定时间并且不会污染文字。 AFAICT,这种行为似乎是完全有效的,但绝对会让您出乎意料。