(这是对Raku rebless doesn't work with inherited classes anymore的跟进)

我试图提出一个更复杂的用例,但是无法使代码正常工作。

这个想法是一个Person类,其中有针对child和Adult的mixin子类。我们有一个Child对象,并在年龄超过18岁时将类型更改为Adult。

这显然是失败的,因为成人是 parent 的混合体,而不是 child 的混合体:

class Person
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    # Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }

  method can-vote
  {
    ...;
  }
}

constant Adult = Person but role { method can-vote { True  } }

constant Child = Person but role
{
  method can-vote { False }
  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless(self, Adult) if $.age == 18;
  }

}

BEGIN Child.^set_name('Child');
BEGIN Adult.^set_name('Adult');

my $tom   = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

但是它部分运行:
Age  Can-Vote  Class
  0   False    Child
  1   False    Child
  2   False    Child
  3   False    Child
  4   False    Child
  5   False    Child
  6   False    Child
  7   False    Child
  8   False    Child
  9   False    Child
 10   False    Child
 11   False    Child
 12   False    Child
 13   False    Child
 14   False    Child
 15   False    Child
 16   False    Child
 17   False    Child
Incompatible MROs in P6opaque rebless for types Child and Adult
  in method happy-birthday at ./vote-error line 28

只需设置一个类和一个mixin就可以了:
class Child
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }

  method can-vote
  {
    False;
  }
}

constant Adult = Child but role { method can-vote { True } }

BEGIN Adult.^set_name('Adult');

my $tom = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

除非它不起作用:
 Error while compiling vote-error1
Illegally post-declared type:
    Adult used at line 10

我明白了。粗线指的是Adult,尚未声明。因此,我尝试对类(class)进行 stub :
class Child { ... }

constant Adult = Child but role { method can-vote { True } }

class Child
{
  has Int $.age is rw = 0;

  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }

  method can-vote
  {
    False;
  }
}

BEGIN Adult.^set_name('Adult');

my $tom = Child.new;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

但是 stub 和继承彼此不喜欢:
===SORRY!=== Error while compiling vote-error2
'Child+{<anon|1>}' cannot inherit from 'Child' because 'Child' isn't composed yet (maybe it is stubbed)

然后,我尝试添加一个新的mixin来避免循环引用问题:
class Child
{
  has Int $.age is rw = 0;

  method can-vote
  {
    False;
  }
}

constant Adult = Child but role { method can-vote { True } }
BEGIN Adult.^set_name('Adult');

role still-a-child
{
  method happy-birthday
  {
    $.age++;
    Metamodel::Primitives.rebless($, Adult) if $.age == 18;
  }
}

my $tom = Child.new but still-a-child;

say "Age  Can-Vote  Class";

for ^20
{
  say "{ $tom.age.fmt('%3d') }   { $tom.can-vote }    { $tom.^name }";
  $tom.happy-birthday;
}

但这也失败了:
Age  Can-Vote  Class
  0   False    Child+{still-a-child}
  1   False    Child+{still-a-child}
  2   False    Child+{still-a-child}
  3   False    Child+{still-a-child}
  4   False    Child+{still-a-child}
  5   False    Child+{still-a-child}
  6   False    Child+{still-a-child}
  7   False    Child+{still-a-child}
  8   False    Child+{still-a-child}
  9   False    Child+{still-a-child}
 10   False    Child+{still-a-child}
 11   False    Child+{still-a-child}
 12   False    Child+{still-a-child}
 13   False    Child+{still-a-child}
 14   False    Child+{still-a-child}
 15   False    Child+{still-a-child}
 16   False    Child+{still-a-child}
 17   False    Child+{still-a-child}
Cannot change the type of a Any type object
  in method happy-birthday at vote-error3 line 26

它的作用是因为$ tom现在不是 child ,而Adult不是我们现在拥有的东西。但是错误消息不是很有帮助。

最后一个基本上与第一个相同。

而且我被困住了。

最佳答案

TL; DR 我描述了几个问题。最后,我将展示一个解决方案,该解决方案可以在最近(2020年)发行的Rakudo上运行。它是您自己的代码的简单变体,但我不够了解,无法保证其正确性,更不用说适当性了[1] [2]。



错误消息来自rebless行:

Metamodel::Primitives.rebless($, Adult) if $.age == 18;
$作为term [3]并不意味着self,而是anonymous state Scalar variable。默认情况下,它包含一个Any,因此是错误消息。它应该是self。[4]

解决了第一个问题后,我们根据使用的Rakudo版本获得了一个新的问题:
  • 旧乐堂:Incompatible MROs in P6opaque rebless for types Child and Adult
  • 较新的Rakudo:New type Adult for Child is not a mixin type

  • 就像我们刚刚解决的第一个错误消息一样,这两个也是由rebless语句触发的。[5]

    我们必须解决这两个问题。

    在较新的Rakudo中,如果我使用您的“添加新的mixin”代码,则仅解决Cannot change the type of a Any type objectnot a mixin type问题还不够。我刚收到Incompatible MROs错误。

    相反,使用替代代码来修复较旧的Rakudo上的Incompatible MROs问题会导致not a mixin type,除非已正确解决了该问题。 (在此答案的原始版本中,我解决了Incompatible MROs问题-然后忽略了对更新的Rakudo进行测试!)

    您对Incompatible MROs错误的诊断是“这显然失败了,因为AdultPerson上的mixin,而不是Child上的”。我读了一下,看了一眼代码,相信了您,然后继续前进。但是后来,我使用您编写的尝试解决该问题的代码回到了同样的问题。是什么赋予了?

    根据我的实验,似乎不仅“to”类(其类将成为要重赋对象的新类)必须具有与我希望得到的对象兼容的MRO(根据我的期望,就像类一样)继承),但“发件人”对象(受祝福的对象)不能兼具:
  • 基于具有属性的类。
  • 已混入。

  • (我不知道这是一个可以解决的错误,还是不可避免的限制。我确实知道,最近(2020年)Rakudo使用前面SO中提供的两种Jonathan代码变体都具有此约束。)

    这意味着“添加新的mixin以避免循环引用问题”(“桩和继承互不相同”)无法解决您的问题。

    相反,我回到了您的“仅一个类和一个mixin”的尝试(最后以您最初编写的形式Illegally post-declared type结束),并尝试了另一种方法来解决该错误。

    您的“仅一个类和一个mixin”代码的以下变体适用于Rakudo v2020.01.114.gcfe.2.cdc.56。我所做的只是将Adult常量转换为变量。我为其余代码编写了...,与您的代码相同:
    my $Adult;
    
    ...
        Metamodel::Primitives.rebless(self, $Adult) if $.age == 18;
    ...
    
    $Adult = Child but role { method can-vote { True } }
    $Adult.^set_name('Adult');
    
    ...
    

    Hth。

    脚注

    [1]乔纳森的解决方案在最近的SO中使用了Adult的编译时构造。我的解决方案遵循Jonathan的示例,不同之处在于它在运行时构造了毫不费力的目标$Adult。面对@JonathanWorthington引入的新优化,我不确定这在技术上是否安全。我将尝试“召集”他对此发表评论。

    [2]除了此脚注之外,我的回答还没有涉及使用rebless的智慧。我马上想到了两个问题。首先是turophilia,它是可靠的功能,即使您需要询问最近的SO,它对于您来说显然也是至关重要的。 (随之而来的是亲子癖。也就是说,我们目前在使语言Raku和实现Rakudo趋于成熟的方法上存在漏洞。就我们其中一位所写的代码会导致漏洞被填补的情况,我们所有人都应该心存感激。 。)其次是MOP的可靠文档,因为(据我所知)某些关键文档违反了根据roast而不是"largely reflects the metaobject system as implemented by the Rakudo Raku compiler"将自己约束为Raku规范的一般规则。我只是解决错误,直到您的代码在2020版本的Rakudo上编译并没有错误地运行。

    [3]参见什么是术语? this comment中的某些上下文链接。

    [4]某些人可能会认为,如果$.foo.fooself,则$必须是self。如果raku具有用于大多数编程语言的典型的无上下文标记,则这种想法将是合理的假设。而且,它通常也适用于Raku代码,就像它甚至适用于自然语言一样。 (如果英语标记“my”后跟“self”,则它的含义可能与“myself”相同。)但是Raku的语法故意将context sensitivityscannerless parsingmaximal munch组合在一起,以支持创建比平时更自然的感觉语言用于编程语言。在这里,我们看到一个例子。在“词条位置” [3]中,输入的$.foo被识别为单个 token ,而不是两个 token ($后跟.foo),而输入的$,...被识别为两个 token ($后跟列表分隔符,)而不是一个。

    [5]所有这些错误消息都是在Rakudo靠近金属的部分中生成的。如果您使用MoarVM作为后端,则它们来自其P6opaque.c文件。

    关于raku - Raku霸道和多类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60051089/

    10-10 02:45