Raku rebless 和多个类

Arn*_*mer 6 raku

(这是对:Raku rebless 不再使用继承类的后续操作

我试图想出一个更复杂的用例,但我无法让代码工作。

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

这个显然失败了,因为 Adult 是 Parent 上的 mixin,而不是 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;
}
Run Code Online (Sandbox Code Playgroud)

但它部分运行:

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

只用一个类和一个 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;
}
Run Code Online (Sandbox Code Playgroud)

除了它不起作用:

 Error while compiling vote-error1
Illegally post-declared type:
    Adult used at line 10
Run Code Online (Sandbox Code Playgroud)

我明白了。rebless 行是指尚未声明的 Adult。所以我尝试存根类:

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

但是存根和继承不喜欢彼此:

===SORRY!=== Error while compiling vote-error2
'Child+{<anon|1>}' cannot inherit from 'Child' because 'Child' isn't composed yet (maybe it is stubbed)
Run Code Online (Sandbox Code Playgroud)

然后我尝试添加一个新的 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;
}
Run Code Online (Sandbox Code Playgroud)

但这也失败了:

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

它确实如此,因为 $tom 现在不是 Child ,而 Adult 不是我们现在拥有的混合体。但是错误消息不是很有帮助。

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

我被卡住了。

rai*_*iph 7

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

Cannot change the type of a Any type object

错误消息来自以下rebless行:

Metamodel::Primitives.rebless($, Adult) if $.age == 18;
Run Code Online (Sandbox Code Playgroud)

A$作为术语[3]并不意味着self而是一个匿名状态Scalar变量。默认情况下,它包含一个Any,因此是错误消息。应该是self[4]

解决了第一个问题后,根据使用的 Rakudo 版本,我们得到了一个新问题:

  • 老乐道:Incompatible MROs in P6opaque rebless for types Child and Adult

  • 新乐堂:New type Adult for Child is not a mixin type.

和我们刚刚修复的第一条错误信息一样,这两条也是由rebless语句触发的。[5]

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

在新的Rakudo,固定Cannot change the type of a Any type object not a mixin type问题是不够的,如果我用你的“添加新的混入”的代码; 我只是得到Incompatible MROs错误。

相反,使用备用代码,修复了Incompatible MROs上一个旧Rakudo导致问题的not a mixin type,除非问题妥善解决。(在这个答案的原始版本中,我解决了这个Incompatible MROs问题——然后忽略了在更新的 Rakudo 上进行测试!)

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

根据我的实验,似乎不仅“to”类(其类是被重新祝福的对象的新类)必须有一个与被重新祝福的对象兼容的 MRO,根据我的期望(如类继承)但“从”对象(一个是reblessed)不能

  • 基于具有属性的类。

  • 已经混进去了。

(我不知道这是一个可以修复的错误还是一个不可避免的限制。我知道最近(2020 年)Rakudo 使用之前 SO 中提供的代码 Jonathan 的两种变体都有这个限制。)

这意味着“添加新的 mixin 以避免循环引用问题”(“存根和继承不喜欢彼此”)并不能解决您的问题。

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

“只有一个类和一个混合”代码的以下变体适用于 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');

...
Run Code Online (Sandbox Code Playgroud)

嗯。

脚注

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

[2]除了这个脚注之外,我的回答没有提到使用rebless. 我立刻想到了两个问题。首先是考虑到turophilia 的可靠功能,这显然对您至关重要,甚至需要询问您最近的 SO。(有了它,metaaturophilia。也就是说,我们目前在使 Raku(语言)和 Rakudo(实现)成熟的方法上存在漏洞。我们中的一个人编写的代码导致漏洞被填补,我们都可以感激.) 其次是MOP 的可靠文档,因为(据我所知)一些关键文档打破了根据烤将自己约束到 Raku 规范的一般规则,取而代之的是“很大程度上反映了由 Rakudo Raku 编译器实现的元对象系统”。我只是解决错误,直到您的代码在 2020 年版本的 Rakudo 上编译和运行没有错误为止。

[3]请参阅什么是术语?此评论中的某些上下文相关联。

[4]有些人可能会假设,如果$.foo.fooself,那么$ 一定self。如果 raku 具有用于大多数编程语言的典型的上下文无关标记化,那么这种想法将是一个合理的假设。此外,它通常也适用于 Raku 代码,就像它通常适用于自然语言一样。(如果英文标记“my”后跟“self”,那么它可能与“myself”的意思相同。)但 Raku 的语法故意结合上下文敏感、无扫描解析最大咀嚼,以支持创建更自然的感觉语言这对于编程语言来说是典型的。在这里我们看到一个例子。在 ”[3]输入$.foo被识别为单个标记而不是两个($后跟.foo),而输入$,...被识别为两个标记($后跟列表分隔符运算符,)而不是一个。

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