*为啥*列表赋值会使其左侧变平?

Posted

技术标签:

【中文标题】*为啥*列表赋值会使其左侧变平?【英文标题】:*why* does list assignment flatten its left hand side?*为什么*列表赋值会使其左侧变平? 【发布时间】:2021-11-12 11:55:45 【问题描述】:

我知道列表赋值会使其左侧变平:

my ($a, $b, $c);
($a, ($b, $c)) = (0, (1.0, 1.1), 2);
say "\$a: $a"; # OUTPUT: «$a: 0»
say "\$b: $b"; # OUTPUT: «$b: 1 1.1»    <-- $b is *not* 1
say "\$c: $c"; # OUTPUT: «$c: 2»        <-- $c is *not* 1.1

我也明白我们可以使用:($a, ($b, $c)) := (0, (1.0, 1.1)) 来获得非扁平化行为。

我不明白的是为什么在列表分配过程中左侧会变平。这是两个问题:首先,这种扁平化行为如何与语言的其他部分相适应?其次,如果左侧不展平,自动展平是否允许任何不可能的行为?

关于第一个问题,我知道 Raku 历来有很多自动扁平化行为。在Great List Refactor 之前,像my @a = 1, (2, 3), 4 这样的表达式会自动展平其右侧,从而生成数组[1, 2, 3, 4];同样,map 和许多其他迭代构造会使它们的论点变平。然而,在 GLR 之后,Raku 基本上不会在没有被告知的情况下将列表变平。事实上,我想不出在没有flat.flat|*@ 以某种方式参与的情况下,Raku 会变平的任何其他情况。 (@_ 创建一个隐含的*@)。我是否遗漏了什么,或者列表分配中的 LHS 行为真的与后 GLR 语义不一致?这种行为是历史上的怪事,还是仍然有意义?

关于我的第二个问题,我怀疑列表分配的扁平化行为可能有助于支持懒惰。例如,我知道我们可以使用列表赋值来使用惰性列表中的某些值,而无需全部生成/计算它们——而将:= 与列表一起使用将需要计算所有 RHS 值。但我不确定是否/如何自动展平 LHS 来支持这种行为。

我还想知道自动展平是否与 = 可以传递给元运算符这一事实有关 - 不像 :=,如果与元运算符一起使用会产生“过于繁琐”的错误。但我不知道自动展平如何/是否使 = 不那么“繁琐”。

[编辑:早在2015-05-02,我就发现 IRC 对“(GLR 保留的)列表分配正在扁平化的决定”的引用,因此很明显,这个决定是故意的并且是有充分理由的。但是,到目前为止,我还没有找到这个理由,并且怀疑它可能是在面对面的会议上决定的。所以我希望有人知道。]

最后,我还想知道 LHS 在概念层面上是如何扁平化的。 (我并不是指具体的 Rakudo 实现;我的意思是作为一种心理模型)。以下是我一直在考虑绑定与列表分配的方式:

my ($a, :$b) := (4, :a(2)); # Conceptually similar to calling .Capture on the RHS
my ($c, $d, $e);
($c, ($d, $e) = (0, 1, 2);  # Conceptually similar to calling flat on the LHS

除了实际上在第 1 行的 RHS 上调用 .Capture 有效,而在第 3 行的 LHS 上调用 flat 会引发 Cannot modify an immutable Seq 错误 - 我觉得这很令人困惑,因为我们一直在压扁Seqs。那么有没有更好的思维模型来思考这种自动扁平化行为?

提前感谢您的帮助。作为对improve the related docs 的工作的一部分,我正在努力更好地理解这一点,因此您可以提供的任何见解都会支持这项工作。

【问题讨论】:

“(GLR 保留的)列表分配扁平化的决定”。看看这个例子,我很确定 jnthn 是在谈论 RHS 被展平,而不是左边。 为感兴趣的人发布 REPL 代码和返回值。如果没有预先声明my ($a, $b, $c);(带或不带括号),您会得到以下结果:&gt; my ($a, $b, $c) = (0, (1.0, 1.1), 2); 返回(0 (1 1.1) 2);而&gt; my (($a, $b), $c) = (0, (1.0, 1.1), 2);my ($a, ($b, $c)) = (0, (1.0, 1.1), 2); 返回(0 (1 1.1))。 [$*VM is moar (2021.06)]. 预声明 my ($a, $b, $c); 你会看到以下内容:&gt; my ($a, $b, $c);($a, $b, $c) = (0, (1.0, 1.1), 2); 返回 (0 (1 1.1) 2)(与没有预声明相同),但是 &gt; my ($a, $b, $c);(($a, $b), $c) = (0, (1.0, 1.1), 2); 返回 ((0 (1 1.1)) 2) 和 @987654355 @ 返回(0 ((1 1.1) 2))。 [$*VM is moar (2021.06)]. @jubilatious1 您上面提到的返回值是正确的,但可能具有误导性。它们是正确的,因为它们是 REPL 打印的内容,并且是赋值表达式的 result。但请注意,当 REPL 打印该结果时,它仍被分组为一个带括号的 single 表达式。但是$a$b$c的值在这三种情况下都是一样的! 这就是我在 REPL 中看到的带有预声明的内容:&gt; my ($a, $b, $c);(($a, $b), $c) = (0, (1.0, 1.1), 2); 返回 ((0 (1 1.1)) 2),并逐个遍历变量给出:&gt; say $a; 返回 0&gt; say $b; 返回 @ 987654365@ 和 &gt; say $c; 返回 2 【参考方案1】:

不知何故,以相反的顺序回答问题对我来说更自然。 :-)

其次,如果左侧不展平,自动展平是否允许任何不可能的行为?

将列表的第一个(或前几个)项分配到标量并将其余项放入数组是相对常见的。列表分配下降到左侧的可迭代项是这项工作的原因:

my ($first, $second, @rest) = 1..5;
.say for $first, $second, @rest;'

输出是:

1
2
[3 4 5]

使用尊重结构的绑定,它会更像:

my ($first, $second, *@rest) := |(1..5);

首先,这种扁平化行为如何与语言的其他部分相适应?

一般来说,结构没有意义的操作会将其扁平化。例如:

# Process arguments
my $proc = Proc::Async.new($program, @some-args, @some-others);

# Promise combinators
await Promise.anyof(@downloads, @uploads);

# File names
unlink @temps, @previous-output;

# Hash construction
my @a = x => 1, y => 2;
my @b = z => 3;
dd hash @a, @b;  # :x(1), :y(2), :z(3)

当然,列表分配也可以以一种尊重结构的方式定义。这些事情的发生往往有多种原因,但一方面,当你确实想做结构化的事情时,语言已经有了绑定,另一方面,my ($first, @rest) = @all 有点太常见了,无法让想要它的人放弃绑定/slurpy电动工具路径。

【讨论】:

以上是关于*为啥*列表赋值会使其左侧变平?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我从多个“if”语句中收到“无效赋值左侧”错误?

为啥在verilog中要定义wire?

我在做多重响应分析时,为啥我编辑好变量之后,在定义变量集时在左侧列表框里没有变量呀。求解,谢谢。

为啥在 subView 中添加 UILabel 会使其像素化

为啥将我的模块分成多个文件会使其变慢?

IOS中的UIScrollView