急切的奇怪行为

Strange behaviour of eager take

天生懒惰

D:\>6e "my @bar = 'a', 'b', 'c'; sub foo( @b ) { my $bar = 0; gather loop { print "*"; take ($bar, @b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( @bar )"
*(0 a)*(1 b)*(2 c)

到目前为止,符合预期。现在,让我们一起期待吧。

D:\>6e "my @bar = 'a', 'b', 'c'; sub foo( @b ) { my $bar = 0; eager gather loop { print "*"; take ($bar, @b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( @bar )"
***(3 a)(3 b)(3 c)

为什么数值都是“3”?好像,我不知道,闭包在一阵不合逻辑的情况下消失了。 take-rw 也不行。

D:\>6e "my @bar = 'a', 'b', 'c'; sub foo( @b ) { my $bar = 0; eager gather loop { print "*"; take-rw ($bar, @b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( @bar )"
***(3 a)(3 b)(3 c)

我可以缓解

D:\>6e "my @bar = 'a', 'b', 'c'; sub foo( @b ) { my $bar = 0; eager gather loop { print "*"; take-rw ($bar + 0, @b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( @bar )"
***(0 a)(1 b)(2 c)

但为什么我必须这样做?

编辑:所以,问题归结为,

>6e "my $bar = 0; .say for gather { take ($bar,); $bar++; take ($bar,) }"
(0)
(1)

>6e "my $bar = 0; .say for eager gather { take ($bar,); $bar++; take ($bar,) }"
(1)
(1)

仍然无法解释,为什么 eagerlazy 情况下的行为存在差异。

Holli 接受了引用 Brad 的两条评论的答案。我故意让那一个简洁。

但是你正在阅读这篇文章。1所以我将在相反的方向上回答这个问题。

($bar,...) 是包含 $bar 的列表,而不是 $bar 的值

问题中显示的行为实际上是关于容器与值的,以及列表文字如何存储容器,而不是它们的值1:

my $bar = 0;
my $list = ($bar,);
say $list[0];       # 0
$bar = 1;
say $list[0];       # 1

懒惰与急切的方面只是做他们应该做的事情。强调第一点可能足以激发您关注容器与价值。也许这会让您快速了解 Holli 的代码中出了什么问题。但也许不是。1所以我会继续。

在懒惰的情况下会发生什么?

惰性列表等待直到需要一个值,然后再尝试生成它。然后,它 只做必要的工作 并暂停,让出控制权,直到稍后要求它产生另一个值。

在 Holli 的代码中,for 循环需要值。

第一次围绕 for 循环时,它需要来自 lazy 表达式的值。这反过来要求 gather' 表达式的值。后者然后计算直到 take,此时它创建了一个列表,其第一个元素是 container $bar。此列表是 take.

的结果

然后 .print 打印第一个列表。在打印时,$bar 仍然包含 0。 ($bar 的第一个增量尚未发生。)

第二次围绕 for 循环,重新进入包含 takeloop)的内部控制结构。发生的第一件事是 $bar 第一次递增。然后检查循环退出条件(并失败),因此第二次循环开始。另一个列表被创建。然后是 taked.

当打印第二个列表时,它的第一个元素,即 $bar 容器,打印为 1,而不是 0,因为在那个时候,已经递增, $bar 现在包含 1.

(如果 Holli 编写了保留第一个列表的代码,并在刚刚打​​印完 first 列表后再次打印 now第二个列表,他们会发现 first 列表 also 现在印有 1,不再是 0。因为所有 taked 列表都有 same $bar 容器作为它们的第一个元素。)

第三个列表也是如此。

打印完第三个列表后,for 循环要求在 gather 处进行第四次循环。这将在 take 语句之后的语句处重新输入 loop$bar 第三次增加到 3,然后 last if $bar > 2; 条件触发,退出循环(因此表达式为 gather,最终整个.print for ...声明)。

在急切的情况下会发生什么?

All gathering 在 printing 的 any 之前完成。

最后,for 构造具有三个列表的序列。它尚未调用任何 .print 个调用。 gather 中的 loop 第三次离开 $bar 包含 3.

接下来,对三个列表中的每一个调用 .print$bar 包含 3 所以它们都以 3 作为第一个元素打印。

切换到数组解决问题

我认为处理这个问题的惯用方法是从列表文字切换到数组文字:

[$bar, @bar[$bar]]
# instead of
($bar, @bar[$bar])

之所以有效,是因为与 list 文字不同,array 文字将作为容器的元素视为 r 值2,即它复制 包含在 容器中的值 =177=]out 那个容器,而不是存储容器本身。

恰好值被复制到另一个newScalar容器中。 (这是因为 all 新非本地数组的元素是新鲜的 Scalar 容器;这是使数组不同于列表的主要因素之一。)但是效果此上下文与将值直接复制到数组中相同,因为 $bar 中包含的值随着事情的进行而变化不再重要。

结果是三个数组的第一个元素最终分别包含012,[=中包含的三个值13=] 在实例化每个数组时。

通过切换到表达式解决问题

正如 Holli 所说,写 $bar + 0 也有效。

事实上 任何 表达式都可以,只要它不是单独的 $bar

当然,表达式需要工作,并且 return 正确的值。我认为 $bar.self 应该有效,并且 return 正确的值,无论绑定或分配什么值 $bar

(虽然它确实读起来有点奇怪;如果 $bar 绑定到 Scalar 容器!事实上,在一个更反直觉的转折中,甚至 $bar.VAR,它使用 .VAR,一个 "Returns the underlying Scalar object, if there is one." 的方法,最终仍然被视为 r 值 而不是!)

文档需要更新吗?

以上是以下的完全合乎逻辑的结果:

  • Scalar是什么;

  • 列表文字对 Scalar 的作用;

  • 惰性处理与急切处理的含义。

如果文档薄弱,大概是在对最后两个方面之一的解释中。看起来主要是列表文字方面。

doc's Syntax page has a section on various literals, including Array literals, but not list literals. The doc's Lists, sequences, and arrays确实有一个列表文字部分(而不是关于数组的部分)但它没有提到他们用 Scalars.

做什么

大概这值得关注。

列表、序列和数组 页面还有一个 Lazy lists 部分可能会更新。

将以上内容放在一起看起来最简单的文档修复可能是更新 列表、序列和数组 页面。

脚注

1 在这个答案的前几个版本中(1, 2,我试图让 Holli 反思容器与价值的影响。但这失败了它们可能也不适合你。如果你不熟悉 Raku 的容器,请考虑阅读:

  • Containers,官方文档的"low-level explanation of Raku containers".

  • Containers in Perl 6,Elizabeth Mattijsen 为熟悉 Perl 的人编写的关于 Raku 基础知识系列文章的第三篇。

2 Wikipedia's discussion of "l-values and r-values" 中的一些细节不适合 Raku,但一般原则是相同的。

在 Raku 中,大多数值都是不可变的。

my $v := 2;
$v = 3;
Cannot assign to an immutable value

做变量,其实你知道变量,有一种东西叫做Scalar

默认情况下 $ 变量实际上绑定到新的 Scalar 容器。
(这是有道理的,因为它们被称为标量变量。)

my $v;
say $v.VAR.^name; # «Scalar»

# this doesn't have a container:
my $b := 3;
say $v.VAR.^name; # «Int»

您可以绕过这个 Scalar 容器。

my $v;

my $alias := $v;
$alias = 3;

say $v; # «3»
my $v;

sub foo ( $alias is rw ){ $alias = 3 }

foo($v);

say $v; # «3»

您甚至可以在列表中传递它。

my $v;

my $list = ( $v, );

$list[0] = 3;

say $v; # «3»

这是您所看到的行为的基础。


问题是您实际上并不想传递容器,而是想传递容器中的值。

所以你要做的就是去容器化它。

对此有几个选项。

$v.self
$v<>
$v[]
$v{}

最后几个只对 ArrayHash 容器有意义,但它们也适用于 Scalar 个容器。

我建议使用 $v.self$v<> 进行去容器化。

(从技术上讲,$v<>$v{qw<>} 的缩写,因此它是 Hash 容器的缩写,但似乎一致认为 $v<>通常可以用于此目的。)


当您执行 $v + 0 时,您所做的是创建一个不在 Scalar 容器中的新值对象。

由于它不在容器中,因此传递的是值本身而不是容器。


在懒惰的情况下你没有注意到这种情况发生,但它仍在发生。

只是你在容器从你下面变出来之前打印了容器中的当前值。

my @bar = 'a', 'b', 'c';
sub foo( @b ) {
  my $bar = 0;
  gather loop {
    print "*";
    take ($bar, @b[$bar]);
    $bar++;
    last if $bar > 2;
  }
};

my \sequence = foo( @bar ).cache; # cache so we can iterate more than once

# there doesn't seem to be a problem the first time through
.print for sequence; # «*0 a*1 b*2 c»

# but on the second time through the problem becomes apparent
.print for sequence; # «3 a3 b3 c»

sequence[0][0] = 42;
.print for sequence; # «42 a42 b42 c»

say sequence[0][0].VAR.name; # «$bar»
# see, we actually have the Scalar container for `$bar`

如果您在生成的序列上使用类似 .rotor 的内容,您也会注意到这一点。

.put for foo( @bar ).rotor(2 => -1);

# **1 a 1 b
# *2 b 2 c

它仍然是懒惰的,但是序列生成器需要在您第一次打印之前创建两个值。所以你最终在第二个 take.
之后打印 $bar 中的内容 在第二个 put 中打印第三个 take.
之后的内容 特别注意与 b 关联的数字从 1 更改为 2
(我使用 put 而不是 print 来拆分结果。它与 print 具有相同的行为。)


如果你去容器化,你得到的是值而不是容器。
因此,在此期间 $bar 发生什么并不重要。

my @bar = 'a', 'b', 'c';
sub foo( @b ) {
  my $bar = 0;
  gather loop {
    print "*";
    take ($bar.self, @b[$bar]); # <------
    $bar++;
    last if $bar > 2;
  }
};

my \sequence = foo( @bar ).cache;

.print for sequence; # «*0 a*1 b*2 c»
.print for sequence; # «0 a1 b2 c»

# sequence[0][0] = 42; # Cannot modify an immutable List

# say sequence[0][0].VAR.name; # No such method 'name' for invocant of type 'Int'.


.put for foo( @bar ).rotor(2 => -1);

# **0 a 1 b
# *1 b 2 c

根据@BradGilbert:

It is only about containers and values.

The reason the lazy case works is because you don't notice that the old values changed out from under you, because they are already printed.

请参阅 Brad 和 raiph 的回答以进一步讨论。