推后是不是影响标量值...(Raku)

Posted

技术标签:

【中文标题】推后是不是影响标量值...(Raku)【英文标题】:Scalar value being affected after push, or not... (Raku)推后是否影响标量值...(Raku) 【发布时间】:2020-02-03 13:48:45 【问题描述】:

我很难理解被推送的Scalar 容器所持有的值何时以及为何在推送后受到影响。我将尝试在两个程式化的示例中说明我在更复杂的上下文中遇到的问题。

*示例 1 * 在第一个示例中,标量 $i 被推送到数组 @b 作为 List 的一部分。推送之后,标量保存的值在 for 循环的后续迭代中使用 $i++ 指令显式更新。这些更新会影响数组@b 中的值:在for 循环结束时,@b[0;0] 等于3,不再等于2

my @b;
my $i=0;
for 1..3 -> $x 
  $i++;
  say 'Loose var $i: ', $i.VAR.WHICH, " ", $i.VAR.WHERE;
  if $x == 2 
     @b.push(($i,1));
     say 'Pushed $i   : ', @b[0;0].VAR.WHICH, " ", @b[0;0].VAR.WHERE;
  

say "Post for-loop";
say "Array       : ", @b;
say 'Pushed $i   : ', @b[0;0].VAR.WHICH, " ", @b[0;0].VAR.WHERE;

输出示例1:

Loose var $i: Scalar|94884317665520 139900170768608
Loose var $i: Scalar|94884317665520 139900170768648
Pushed $i   : Scalar|94884317665520 139900170768648
Loose var $i: Scalar|94884317665520 139900170768688
Post for-loop
Array       : [(3 1)]
Pushed $i   : Scalar|94884317665520 139900170768688

* 示例 2 * 在第二个示例中,标量 $i 是循环变量。即使$i 在被推送后更新(现在是隐式而不是显式),数组@c$i 的值 推送后更改;即在for循环之后,它仍然是2,而不是3

my @c;
for 1..3 -> $i 
  say 'Loose var $i: ', $i.VAR.WHICH, " ", $i.VAR.WHERE;
  if $i == 2 
     @c.push(($i,1));
     say 'Pushed $i   : ', @c[0;0].VAR.WHICH, " ", @c[0;0].VAR.WHERE;
  

say "Post for-loop";
say "Array       : ", @c;
say 'Pushed $i   : ', @c[0;0].VAR.WHICH, " ", @c[0;0].VAR.WHERE;;

输出示例2:

Loose var $i: Scalar|94289037186864 139683885277408
Loose var $i: Scalar|94289037186864 139683885277448
Pushed $i   : Scalar|94289037186864 139683885277448
Loose var $i: Scalar|94289037186864 139683885277488
Post for-loop
Array       : [(2 1)]
Pushed $i   : Scalar|94289037186864 139683885277448

问题:为什么示例1中@b中的$i在推送后更新,而示例2中@c中的$i没有?

编辑: 在@timotimo 的评论之后,我在示例中包含了.WHERE 的输出。这表明$i 的(WHICH/逻辑)标量标识保持不变,而其内存地址通过各种循环迭代发生变化。但它没有解释为什么在示例 2 中推送的标量仍然绑定到相同的 WHICH 身份以及旧地址(“448)”。

【问题讨论】:

我可以告诉你为什么 WHICH 似乎保持不变;查看实现:github.com/rakudo/rakudo/blob/master/src/core.c/Scalar.pm6#L8 - 它仅取决于所使用的描述符,这是一个包含变量名称和类型约束等内容的小对象。如果您使用.WHERE 而不是.WHICH,您可以看到标量实际上每次循环都是不同的对象。发生这种情况是因为尖块被“调用”,并且每次调用都“绑定”了签名。 @raiph 在循环期间,示例 1 显示了与示例 2 相同的模式:两者都具有由 .WHERE 报告的更改地址,这说明了,我同意。但它本身并不能解释为什么示例 2 的结局与示例 1 不同。 【参考方案1】:

标量值只是一个容器。您可以将它们视为一种智能指针,而不是原始值。

如果你做作业

$foo = "something"; #or
$bar++;

您正在更改标量值,容器保持不变。

考虑

my @b; 
my $i=0; 
for 1..5 -> $x  
  $i++; 
  @b.push(($i<>,1)); # decontainerize $i and use the bare value
 
say @b;

my @b; 
my $i=0; 
for 1..5 -> $x  
  $i := $i + 1;  # replaces the container with value / change value
  @b.push(($i,1)); 
 
say @b;

两者都按预期工作。但是:在这两种情况下,列表中的事物不再是可变的,因为没有容器。

@b[4;0] = 99; 

因此会死。所以只需使用循环变量,对吗?

没有。

for 1..5 -> $x  
  @b.push(($x,1)); # 
 
@b[4;0] = 99; #dies

即使我们遍历一个可变事物列表。

my $one = 1;
my $two = 2;
my $three = 3;
my $four = 4;
my $five = 5;

for ($one, $two, $three, $four, $five) -> $x  
  @b.push(($x,1)); 
 
@b[4;0] = 99; #dies

所以这里没有发生别名,而是循环变量始终是同一个容器,并获取来自其他容器的分配值。

我们可以这样做。

for ($one, $two, $three, $four, $five) <-> $x  
  @b.push(($x,1)); 
 
@b[4;0] = 99; # works

for ($one, $two, $three, $four, $five) -> $x is rw  
  @b.push(($x,1)); 
 
@b[4;0] = 99; # works too

使“事物”可变的一种方法是使用中间变量。

for 1..5 -> $x  
  my $j = $x;
  @b.push(($j,1)); # a new container 
 
@b[4;0] = 99;

工作正常。或者在原始上下文中更短更多

my @b; 
my $i=0; 
for 1..5 -> $x  
  $i++; 
  @b.push((my $ = $i, 1)); # a new anonymous container
 
@b[4;0] = 99;
say @b; # [(1 1) (2 1) (3 1) (4 1) (99 1)]

另见:

https://perl6advent.wordpress.com/2017/12/02/#theoneandonly https://docs.perl6.org/language/containers

【讨论】:

除了($x,1),您还可以使用[$x,1],这创建一个新容器(也适用于1,顺便说一句) @ElizabethMattijsen 但是,“提升”的是阵列吗? 不确定你所说的“提升”是什么意思,但如果你在创造时将价值容器化,那么是的。 @Holli 感谢您的回复。我不确定它是否解决了这个问题。您的回答侧重于容器的可变性,我想我理解。我不明白的是为什么在第一个示例中推送的容器 $i - 或更好:它的值 - 在推送后更新,而在第二个示例中,推送容器的值仍然与当前的值静态绑定的推动。第一个例子对我来说很有意义(容器是指向Int 对象的指针-> Int 在for 循环中被替换-> 容器指向新的Int),但第二个没有。 @Holli 我会试着澄清这个问题。【参考方案2】:

在玩了一段时间并思考了我的上述问题之后,我会打赌答案......这纯粹是我的猜测,所以如果是的话,请随意说这是无意义的,如果你发生了要知道,为什么...

在第一个示例中,$i 是在 for 循环的词法范围之外定义的。因此,$i 独立于循环及其迭代而存在。从循环内部引用$i 时,只有一个$i 会受到影响。正是这个$i 被推入@b,然后在循环中修改其内容。

在第二个示例中,$i 定义在 for 循环的词法范围内。正如@timotimo 指出的那样,每次迭代都会调用指向块的get,就像子例程一样;因此,$i 是为每次迭代新声明的,并作用于相应的块。当在循环内引用$i 时,该引用指向特定于块迭代的$i,当相应的循环迭代结束时,它通常会不复存在。但是因为在某些时候$i 被推送到@c,对特定于块迭代的$i 持有值2 的引用不能在迭代终止后被垃圾收集器删除。它将继续存在...,但在以后的迭代中仍然与 $i 不同。

【讨论】:

@raiph 谢谢。我会去做。也许比我更有洞察力的人可以(重新)正确地表达答案。无论如何,我不会接受我自己的答案是正确的,直到那些知道的人(而不是猜测,比如我自己)确认(或改进)它。

以上是关于推后是不是影响标量值...(Raku)的主要内容,如果未能解决你的问题,请参考以下文章

设置影响嵌入式标量函数的行数

POSIX 信号量父信号量值不受影响

如何使用条件索引在单元格上获取标量值

检查字符串是不是包含子字符串。此外,获取索引和匹配数(Raku)

样式化的组件最终样式通过 <styles /> 标签直接注入到 DOM 中,使用样式标签注入会影响性能吗?

Flink Table API & SQL 自定义 Scalar 标量函数