渴望采取的奇怪行为

时间:2020-01-14 18:48:30

标签: raku

天生懒惰

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情况下的行为有所不同。

3 个答案:

答案 0 :(得分:6)

霍利(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为止,此时创建了一个列表,其第一个元素是容器 $bar。此列表是take的结果。

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

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

打印第二个列表时,其第一个元素(即$bar容器)将打印为1,而不是0,因为在那一点上,{{ 1}}现在包含$bar

(如果Holli编写了保留在第一个列表中的代码,然后又打印了第一个列表,现在又现在,那么在打印第二个列表后,他们将发现 first 列表 现在也打印了1,不再打印1,因为所有0 d列表都有相同 take容器作为它们的第一个元素。)

第三个列表也是如此。

在打印第三个列表之后,$bar循环需要在for处进行第四个遍历。这将在gather语句之后的语句处重新输入looptake第三次增加到$bar,然后触发3条件,退出循环(因此表达式是last if $bar > 2;的对象,最终是整个表达式gather语句。

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

全部 .print for ...的完成要早于gather任何

最后,print构造包含三个列表的序列。它尚未调用任何for调用。 .printloop周围的第三次离开gather包含$bar

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

通过切换到数组解决问题

我认为处理此问题的惯用方式是从列表文字转换为数组文字:

3

之所以起作用,是因为与 list 文字不同, array 文字将 r-value 视为容器的元素2 ,即它复制 包含在该容器的容器中的值,而不是存储容器本身。

碰巧的是,将值复制到另一个 new [$bar, @bar[$bar]] # instead of ($bar, @bar[$bar]) 容器中。 (这是因为新的非本地数组的 all 元素都是新鲜的Scalar容器;这是使数组与列表不同的主要原因之一。)但是在这种情况下,效果是就像将值直接复制到数组中一样,因为随着时间的推移, Scalar 中包含的值也不再发生变化。

结果是三个数组的第一个元素最终分别包含$bar01,这三个数组包含在{{1} }在实例化每个数组时。

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

如霍利(Holli)所言,写2也是可行的。

实际上任何表达式都可以,只要它本身不只是$bar

当然,表达式需要工作,并返回正确的值。我认为$bar + 0应该起作用并返回正确的值,而不管绑定或分配了什么值$bar

(尽管它确实有点奇怪;如果$bar.self绑定到$bar容器,$bar.self本身不是 $bar! ,甚至更违反直觉的扭曲,甚至使用"Returns the underlying Scalar object, if there is one."的方法$bar的{​​{1}}最终仍被视为 r值代替!)

文档需要更新吗?

以上是以下情况的完全逻辑结果:

  • Scalar是什么;

  • 列表字面量如何处理$bar.VAR s;

  • 懒惰与急切处理意味着什么。

如果doc较弱,则大概是在解释最后两个方面之一。看起来主要是列表文字方面。

doc's Syntax page包含有关各种文字的部分,包括数组文字,但没有列表文字。 doc's Lists, sequences, and arrays 确实有一个列表文字部分(而不是Arrays上的一个),但是没有提到它们对.VAR的作用。

大概值得关注。

列表,序列和数组页面上还有一个Lazy lists部分,可能需要更新。

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

脚语

1 在此答案的前几个版本中(12,我试图让Holli反思容器与价值的影响,但失败了对于他们,也许对您也没有用。如果您不熟悉Raku的容器,请考虑阅读:

  • Containers,是官方文档的“ Raku容器的低级说明”。

  • Containers in Perl 6,这是伊丽莎白·马蒂森(Elizabeth Mattijsen)针对熟悉Perl的读者的有关Raku基础知识的系列文章中的第三篇。

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

答案 1 :(得分:2)

在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{}

最后几个仅在 Array Hash 容器上有意义,但它们在 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

它仍然很懒,但是序列生成器需要在首次打印之前创建两个值。因此,您最终在第二个$bar之后打印take中的内容。
然后在第二个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

答案 2 :(得分:1)

每个@BradGilbert:

这只是关于容器和值。

惰性情况起作用的原因是因为您没有注意到旧值已经从您的下面改变了,因为它们已经被打印了。

有关进一步的讨论,请参见Brad和raiph的答案。

相关问题