【问题标题】:`does` versus `but` operators when mixing in a Role into an object in Raku将角色混合到 Raku 中的对象中时的 `does` 与 `but` 运算符
【发布时间】:2021-02-28 16:05:16
【问题描述】:

如果我有一个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,因此可以用于编译时混入(例如class C does R {}),而but 仅用于运行时混入。我也知道but 可以是used with an object(例如my $c = 'question' but False),而does 只能与Role 一起使用。我不是在问这些差异中的任何一个。我唯一的问题是两者在运行时与Role 一起使用时是否有区别。我已经阅读了documentation section on mixing in Role,但没有看到答案。

【问题讨论】:

    标签: oop mixins raku composition rakudo


    【解决方案1】:

    简单地说:

    • 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
    

    请注意,我有点作弊并交换了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 看来,这种行为是完全有效的,但绝对会让你意想不到。

    【讨论】:

    • 谢谢!这是一个很大(而且令人惊讶!)的差异。特别是,我不会仅仅因为我做了my $does = $question does R 而期待$question.answer == 42。我现在可以明白为什么会发生这种情况了,但我很高兴我在这里而不是在调试时了解到这一点!
    • 事实上,这让我感到非常惊讶,因此可能值得将其明确添加到您的答案中。从您写的内容中可以清楚地看出,但如果略读,它也是有人可能会错过的。
    • @codesections 是的,这个想法是你可以单独写一行,写上$foo does Bar 来应用这个角色,而不是说$foo = $foo but Bar。我想使用++$foo$foo + 1 相比大致
    • 另外,FWIW,我以前总是使用but 来处理运行时的东西。意识到差异后,我开始使用 does(我应该更新我在 DateTime::Timezones 上的 Advent 帖子以反映这一点)
    • @jubilatious1 最近被禁止了。
    猜你喜欢
    • 2018-11-07
    • 2018-06-15
    • 2021-07-24
    • 2021-10-27
    • 2023-01-31
    • 2021-11-05
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多