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

cod*_*ons 10 oop composition mixins rakudo raku

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

role R { method answer { 42 } }

Run Code Online (Sandbox Code Playgroud)

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

my $a = 'question' does R;
my $b = 'question' but  R;
Run Code Online (Sandbox Code Playgroud)

它们看起来非常相似:

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

say $a.WHAT;    # OUTPUT: «(Str+{R})»
say $b.WHAT;    # OUTPUT: «(Str+{R})»
Run Code Online (Sandbox Code Playgroud)

这是一个有不止一种方法可以做到这一点的情况吗,这两者的意思是一样的?还是我遗漏了细微的差别?


据我所知,does既操作者和性状,因此当用于编译时混入(例如,可使用class C does R {}),而but仅仅是用于运行时混入。我也明白but可以与对象(例如,my $c = 'question' but Falsedoes一起使用,而只能与Role. 我不是在问这些差异中的任何一个。我唯一的问题是在运行时使用Role. 我已阅读有关在 Role 中混合文档部分,但没有看到答案。

use*_*601 10

简单地说:

  • does 就地修改对象(并且应谨慎使用值类型,请参阅下面的注释)

  • but 返回一个新对象。

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

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
Run Code Online (Sandbox Code Playgroud)

通知我被骗了一下,交换的次序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})
Run Code Online (Sandbox Code Playgroud)

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

my $foo = …;
given $bar {
   when 'a' { $foo does A }
   when 'b' { $foo does B }
   when 'c' { $foo does B }
}
Run Code Online (Sandbox Code Playgroud)

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

does和值类型的警告

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

role Fooish { }

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

say 'foo'.WHAT; # (Str+{Fooish})
Run Code Online (Sandbox Code Playgroud)

这是在编译时发生的替换(因此它不会影响,例如,'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})
Run Code Online (Sandbox Code Playgroud)

应用多个卷需要的时间越长,越多,因此如果将其更改为 ^100000,请准备好等待一段时间。OTOH,这样做but会给你很好的恒定时间并且不会污染文字。AFAICT,这种行为似乎是完全有效的,但绝对可以意外地抓住你。

  • @codesections是的,我们的想法是你可以单独写一行,说“$foo does Bar”来应用这个角色,而不是需要说“$foo = $foo but Bar”。我想“大致”是使用“++$foo”与“$foo + 1”。 (2认同)