*为啥*列表赋值会使其左侧变平?
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
错误 - 我觉得这很令人困惑,因为我们一直在压扁Seq
s。那么有没有更好的思维模型来思考这种自动扁平化行为?
提前感谢您的帮助。作为对improve the related docs 的工作的一部分,我正在努力更好地理解这一点,因此您可以提供的任何见解都会支持这项工作。
【问题讨论】:
“(GLR 保留的)列表分配扁平化的决定”。看看这个例子,我很确定 jnthn 是在谈论 RHS 被展平,而不是左边。 为感兴趣的人发布 REPL 代码和返回值。如果没有预先声明my ($a, $b, $c);
(带或不带括号),您会得到以下结果:> my ($a, $b, $c) = (0, (1.0, 1.1), 2);
返回(0 (1 1.1) 2)
;而> 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);
你会看到以下内容:> my ($a, $b, $c);($a, $b, $c) = (0, (1.0, 1.1), 2);
返回 (0 (1 1.1) 2)
(与没有预声明相同),但是 > 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 中看到的带有预声明的内容:> my ($a, $b, $c);(($a, $b), $c) = (0, (1.0, 1.1), 2);
返回 ((0 (1 1.1)) 2)
,并逐个遍历变量给出:> say $a;
返回 0
,> say $b;
返回 @ 987654365@ 和 > 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电动工具路径。
【讨论】:
以上是关于*为啥*列表赋值会使其左侧变平?的主要内容,如果未能解决你的问题,请参考以下文章
我在做多重响应分析时,为啥我编辑好变量之后,在定义变量集时在左侧列表框里没有变量呀。求解,谢谢。