*为什么*列表赋值将其左侧展平?

*why* does list assignment flatten its left hand side?

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

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)) 来获得 non-flattening 行为。

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

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

关于我的第二个问题,我 怀疑 列表分配的扁平化行为可能以某种方式帮助支持惰性。例如,我知道我们可以使用列表赋值来使用惰性列表中的某些值,而无需全部使用 producing/calculating —— 而对列表使用 := 将需要计算所有 RHS 值。但我不确定 if/how auto-flattening LHS 是否需要支持此行为。

我还想知道 auto-flattening 是否与 = 可以传递给元运算符这一事实有关——不像 :=,它会产生“太繁琐”的错误如果与元运算符一起使用。但我不知道 how/if auto-flattening 让 = 不那么“笨拙”。

[编辑:我早在 2015-05-02 就发现 IRC 提到了“(GLR-preserved) 列表分配正在扁平化的决定”,所以很明显这个决定是有意的和 well-justified。但是,到目前为止,我还没有找到理由并怀疑它可能是在 in-person 会议上决定的。所以我希望有人知道。]

最后,我还想知道 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 弄平。那么有没有更好的心智模型来思考这种 auto-flattening 行为?

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

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

Second, does auto-flattening allow any behavior that would be impossible if the left hand side were non-flattening?

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

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

输出为:

1
2
[3 4 5]

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

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

First, how does this flattening behavior fit in with the rest of the language?

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

# 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 有点太常见了,无法让人们想要它 binding/slurpy电动工具路径。