在特征中混合角色显然不起作用

Mixing-in roles in traits apparently not working

这个例子取自from roast,虽然已经存在了8年:

role doc { has $.doc is rw }

multi trait_mod:<is>(Variable $a, :$docced!) {
    $a does doc.new(doc => $docced);
}

my $dog is docced('barks');
say $dog.VAR;

这个returns Any,没有任何类型的角色混入。显然没有办法到达"doc"部分,尽管特征没有错误。有什么想法吗?

不是一个令人满意的答案,但也许你可以从中取得进步

role doc { 
  has $.doc is rw;
}

multi trait_mod:<is>(Variable:D $v, :$docced!) {
  $v.var.VAR does doc;
  $v.var.VAR.doc = $docced;
}

say $dog;            # ↪︎ Scalar+{doc}.new(doc => "barks")
say $dog.doc;        # ↪︎ barks
$dog.doc = 'woofs';  #
say $dog;            # ↪︎ Scalar+{doc}.new(doc => "woofs")

不幸的是,这有点不对劲,应用特征似乎会导致变量变得不可变。

(此答案基于@guifa 的回答和 JJ 的评论。)

变量特征中使用的成语本质上是$var.var.VAR

虽然大声说出来听起来很有趣,但看起来也很疯狂。它不是,但它至少需要解释,并且 或许 某种 cognitive/syntactic 解脱。

以下是如何理解它的简短版本:

  • $var 作为 trait 参数的名称是有意义的,因为它绑定到 Variablecompiler's-eye 视图一个变量。

  • .var 需要访问 用户视角 给定编译器视角的变量视图。

  • 如果变量是 Scalar 则需要 .VAR 以及 来获取变量而不是它的值包含。 (如果不是 Scalar 也没什么坏处。)

松了一口气?

我会在 mo 中更详细地解释以上内容,但首先,一些缓解措施如何?

也许我们可以引入一种新的 Variable 方法来完成 .var.VAR。但是我认为这将是一个错误,除非该方法的名称非常好,它基本上消除了对该答案下一节中的 $var.var.VAR 咒语解释的需要。

但我怀疑这样的名字是否存在。我想出的每一个名字都在某种程度上让事情变得更糟。即使我们想出了一个完美的名字,充其量也只是勉强值得。

我对您原始示例的复杂性感到震惊。有一个 is 特征调用 does 特征。因此,也许需要一个例程来抽象化这种复杂性和 $var.var.VAR。但是无论如何都有现有的方法来降低双重特征的复杂性,例如:

role doc[$doc] { has $.doc is rw = $doc}
my $dog does doc['barks'];
say $dog.doc; # barks

$var.var.VAR

的详细解释

But $v is already a variable. Why so many var and VARs?

确实如此。 $v 绑定到 Variable class 的实例。还不够吗?

没有,因为 Variable:

  • 用于存储元数据关于一个变量编译。 (也许它应该被称为 Metadata-About-A-Variable-Being-Compiled?开个玩笑。Variable 在特征签名中看起来不错,改变它的名字不会阻止我们需要使用和解释 $var.var.VAR 习语。)

  • 不是我们要找的droid。我们想要变量的用户视角。一个已经被声明和编译,然后被用作用户代码的一部分。 (例如,say $dog...行中的$dog。即使它是BEGIN say $dog...,所以它在编译时运行,$dog也会still 指的是绑定到用户眼视图容器或值的符号。它不会指代 Variable 实例,它只是 编译器眼视图 的数据 与变量相关。)

  • 使编译器和那些编写特征的人的生活更轻松。但它要求特征编写器访问变量的用户视角,以访问或更改用户视角。 Variable.var 属性存储该用户的视线。 (我注意到 roast 测试有一个你忽略的 .container 属性。现在显然已重命名为 .var。我猜那是因为变量可能绑定到不可变值而不是容器,所以名称 .container 被认为具有误导性。)

那么,我们如何到达 $var.var.VAR

让我们从原始代码的变体开始,然后继续。我将从 $dog 切换到 @dog 并从 say 行删除 .VAR

multi trait_mod:<is>(Variable $a, :$docced!) {
  $a does role { has $.doc = $docced }
}

my @dog is docced('barks');
say @dog.doc; # No such method 'doc' for invocant of type 'Array'

几乎有效。一个微小的变化,它的工作原理:

multi trait_mod:<is>(Variable $a, :$docced!) {
  $a.var does role { has $.doc = $docced }
}

my @dog is docced('barks');
say @dog.doc; # barks

我所做的就是在 ... does role ... 行中添加一个 .var。在您的原始代码中,该行正在修改变量的 编译器视图 ,即绑定到 $aVariable 对象。它不会修改用户对变量的看法,即 Array 绑定到 @dog.

据我所知,现在一切都适用于数组和散列等复数容器:

@dog[1] = 42;
say @dog;     # [(Any) 42]
say @dog.doc; # barks

但是当我们尝试使用 Scalar 变量时:

my $dog is docced('barks');

我们得到:

Cannot use 'does' operator on a type object Any.

这是因为.var returns 不管用户的视线变量通常是什么 returns。使用 Array 你会得到 Array。但是使用 Scalar 你会得到 Scalar 包含的值。 (这是 P6 的一个基本方面。它很好用,但你必须在这些场景中了解它。)

所以为了让这个 出现 再次工作,我们还必须添加一对 .VAR。对于 Scalar 以外的任何东西,.VAR 都是 "no op",因此添加它对 Scalar 以外的情况没有任何危害:

multi trait_mod:<is>(Variable $a, :$docced!) {
  $a.var.VAR does role { has $.doc = $docced }
}

现在 Scalar 案例似乎也起作用了:

my $dog is docced('barks');
say $dog.VAR.doc; # barks

(我不得不在 say 行中重新引入 .VAR ,原因与我必须将其添加到 $a.var.VAR ... 行的原因相同。)

如果一切顺利,本回答到此结束。

一个错误

但是有些东西坏了。如果我们尝试初始化 Scalar 变量:

my $dog is docced('barks') = 42;

我们会看到:

Cannot assign to an immutable value

正如@guifa 指出的那样,

It seems that a Scalar with a mixin no longer successfully functions as a container and the assignment fails. This currently looks to me like a bug.