将非标量分配给标量有什么好处?

What's the benefit of assigning non-scalars to scalars?

我有时会看到(对我来说)在变量前面使用错误标记的代码

my $arr  = [1, 2, 3, 4, 5];      # an array
my $lst  = (1, 2, 3, 4, 5);      # a list
my $hash = {a => '1', b => '2'}; # a hash
my $func = -> $foo { say $foo }; # a callable

一切都如预期的那样工作

say $arr[0];    # 1
say $lst[1];    # 2
say $hash<a>;   # 1
say $hash{'b'}; # 2
$func('hello'); # hello

问题 1:为此使用标量容器而不是仅使用 'correct' 容器有什么好处?

我知道 Perl 只让集合存储标量,需要通过数组引用来完成多维数组之类的事情,[...]{...} 是数组和散列 reference 分别是文字。

为了扩展和阐明我在这里的意思,基本上有两种定义事物的方法,按值和按引用:

# "values"
my @arr = (1, 2, 3, 4);
my %hash = (1 => 2, 3 => 4); 

# which are accessed like this:
my $result1 = $arr[0];
my $result2 = $hash{1};

# references (note how the braces canged)
my $aref = [1, 2, 3, 4];
my $href = {1 => 2, 3 => 4};

# or making a reference to existing collections
my $aref2 = \@arr;
my $href2 = \%hash;

# which are accessed like this:
my $result3 = $aref->[0];
my $result4 = $href->{1};

这种疯狂背后的原因是 Perl 集合只真正接受标量,而引用就是这样。 使用引用本质上是一种启用多维数组的方法。

TL;DR,这种区别在 Perl 中很有意义,因为它们有两个截然不同的目的。

问题 2:我们是再次处理类似 Perl 5 的引用文字,还是有其他原因在起作用?

TL;DR 对于计算机和人类,以及 Raku,非标量(复数)也是标量(单数)。 (而反过来可能不成立。)例如,Array 既是复数事物(元素数组)又是 单个事物 Array.当您希望在语法上和静态上强调数据最通用的单数性质时,请使用 $.

这是一个基于@sid_com++评论的开头示例:

my @a = ( 1, 2 ), 42, { :a, :b }
for @a -> $b {say $b}            # (1 2)␤42␤{a => True, b => True}␤ 
for @a -> @b {say @b}            # (1 2)␤Type check failed ...

第一个循环将值绑定到 $b。它是“容错”的,因为它接受 any 值。第二个循环绑定到 @b。任何不执行 Positional 角色的值都会导致类型检查失败。

我的 Raku 相当于你的 Perl 代码

这是您的 Perl 代码的 Raku 翻译:

my @arr = (1, 2, 3, 4);
my %hash = (1 => 2, 3 => 4); 

my $result1 = @arr[0];                          # <-- Invariant sigil
my $result2 = %hash{1};                         # <-- Invariant sigil

my $aref = [1, 2, 3, 4];
my $href = {1 => 2, 3 => 4};

my $aref2 = @arr;                               # <-- Drop `\`
my $href2 = %hash;                              # <-- Drop `\`

my $result3 = $aref[0];                         # <-- Drop `->`
my $result4 = $href{1};                         # <-- Drop `->`

代码有点短。惯用代码可能会更短一些,删除:

  • ref变量。变量 @foo 一个引用。术语(名词)位置 中的 [...] 一个 Array 参考文字。很少或不需要使用标量变量来显式存储引用。

  • 前几行括号;

  • 大多数右大括号后的半冒号是一行中的最后一个代码;

乐的印记是不变的。这里是 two tables providing an at-a-glance comparison of Perl's sigil variation vs Raku's sigil invariance.

为什么要费心印记?

所有印记变体直接对应于将“类型”信息嵌入到对人类、语言和编译器可见的标识符的名称中:

  • foo 告诉 Raku 特征在数据操作的单数和复数方式之间的选择应该根据 运行-时间类型来决定数据。

  • $foo 让 Raku 选择单一行为。例如,一个值可能是一个包含许多值的 List,但它的单一性质却被强调了。

  • &foo 类型检查绑定或分配的值是否扮演 Callable 角色。

  • @foo 让 Raku 选择 Iterable behavior. Also type checks that bound values do the Positional 角色。可以绑定 ListArray,但尝试绑定到 42Hash 会产生类型错误。

  • %foo 让 Raku 选择 Iterable 行为。还要类型检查绑定值是否扮演 Associative 角色。可以绑定 PairBag,但尝试绑定到 42List 会产生类型错误。

接下来我会考虑你对每个印记选项的问题。

削减印记

重复你的例子,但这次“砍掉”印记:

my \arr  = [1, 2, 3, 4, 5];      # an array
my \lst  = (1, 2, 3, 4, 5);      # a list
my \hash = {a => '1', b => '2'}; # a hash
my \func = -> \foo { say foo };  # a callable

这些几乎完全按照预期工作:

say arr[0];     # 1
say lst[1];     # 2
say hash<a>;    # 1
say hash{'b'};  # 2
func.('hello'); # hello

请参阅下面的 $ vs &,了解为什么 func.(...) 而不仅仅是 func(...)。最后一个 nosigil 案例影响不大,因为在 Raku 中通常会这样写:

sub func (\foo) { say foo }
func('hello'); # hello

带有斜线的标识符是 SSA form。也就是说,它们在编译时永久绑定到它们的数据。值类型数据是不可变的。引用也是不可变的(尽管它引用的数据可以更改),因此例如,如果它是一个数组,它将保持相同的数组。

(有关进一步讨论,请参阅 。)

$foo 而不是 @foo?

Raku 支持:

  • 惰性列表。 (这可能非常有用。)

  • 一个布尔值 .is-lazy 方法,指示列表分配 (@foo = ...) 是否应将分配的对象视为惰性对象或急切对象。重要的是,惰性列表允许 return False。 (这也非常有用。)

  • 无限惰性列表。 (还有一件事 非常 有用。)

以上三个功能单独有用,也可以一起使用。但是,尽管 Raku 不尝试以其他方式监管这些功能是适当的,但人们需要遵守规则以避免出现问题。最简单的方法是在重要的时候使用正确的印记,如下所述。

假设 infinite 是一个无限惰性列表 returns False for .is-lazy:

my $foo = infinite;
say $foo[10];        # displays 11th element
my @foo = infinite;

前两行工作正常。第三个挂起,试图将无限数量的元素复制到 @foo.


是一件事情还是很多事情?当然,如果是列表,则两者都是:

my $list = <a b c> ;
my @list = <a b c> ;
my \list = <a b c> ;
.say for $list ;      # (a b c)␤   <-- Treat as one thing
.say for @list ;      # a␤b␤c␤    <-- Treat as plural thing
.say for  list ;      # a␤b␤c␤    <-- Go by bound value, not sigil

上面sigil的选择只是表明你希望语言构造和读者默认采用什么观点。如果你愿意,你可以反转自己:

.say for @$list ;     # a␤b␤c␤
.say for $@list ;     # [a b c]␤
.say for $(list)      # (a b c)␤

赋值不同:

my ($numbers, $letters) = (1, 2, 3), ('a', 'b', 'c');
say $numbers;                                            # (1 2 3)
say $letters;                                            # (a b c)
my (@numbers, @letters) = (1, 2, 3), ('a', 'b', 'c');
say @numbers;                                            # [(1 2 3) (a b c)]
say @letters;                                            # []

@ 变量的赋值会“吞噬”所有剩余的参数。 (与 := 绑定和像 Z= 这样的元操作调用标量语义,即不要发出声音。)

我们在这里看到了另一个不同之处;分配给一个 $ 变量将保留一个 List 一个 List,但是分配给一个 @ 变量将把它的值 变成 [=267] =] @ 变量绑定到的任何容器(默认情况下,Array)。


一个小东西是字符串插值:

my $list := 1, 2;
my @list := 1, 2;
say "$list = $list; \@list = @list"; # $list = 1 2; @list = @list
say "@list @list[] @list[1]";         # @list 1 2 2

$foo 而不是 %foo?

再问一次,是一件事情还是很多事情?如果它是哈希,则两者都是。

my $hash = { :a, :b }
my %hash =   :a, :b ;
my \hash = { :a, :b }
.say for $hash ;      # {a => True, b => True}␤   <-- By sorted keys
.say for %hash ;      # {b => True}␤{a => True}␤  <-- Random order
.say for  hash ;      # {a => True}␤{b => True}␤  <-- Random order

赋值和字符串插值也以类似于@的方式不同。

$foo 而不是 &foo?

本节只是为了完整性。它只显示了使用 $ 的一个原因。我刚刚弥补了这个答案——我不记得看到任何人使用它。

与其他印记替代方案一样,主要区别在于您是否想强调可调用对象的 Callable 性质。

作为设置,请注意 Raku 中的 sub 声明使用 & 印记声明相应的常量标识符:

sub foo (--> Int) { 42 }
say foo;                     # 42
say &foo.signature;          # ( --> Int)
&foo = 99;                   # Cannot modify an immutable Sub...

这意味着如果您使用 & 印记声明一个可变例程变量,您可以在没有 印记的情况下调用它

my &bar = { 99 }
say bar;                     # 99
&bar = { 100 }
say bar;                     # 100

如果你想声明一个可变例程变量并且不允许允许它在没有印记的情况下被轻松调用,你可以用$来声明它:

my Callable $baz = { 101 }
say baz;                     # Undeclared routine: baz
say $baz();                  # 101   <-- Need both sigil and parens

顺便说一句,这就是你得到的原因:

my \func = -> \foo { say foo }
func('hello');  # Variable '&func' is not declared

参考文字

Q2: Are we dealing with Perl 5-like reference literals again, or is something else at play?

尽管有你的例子,知道 Perl(至少我上个世纪知道),并思考你写的东西,但我仍然不清楚你在问什么。

许多编程语言在术语(名词)位置上采用 [...] 作为对文字数组的引用。其他数据结构文字还有其他通用约定。这就是 Raku 所做的。

这样可以写成:

my $structure =
[ 0, [ 99, [ ( 1, 2, 3), { key => [ 4, 5, | < a b >, c => 42 ] } ], ], ] ;

say $structure[1][1][1]<key>[4]<c> ; # 42

你说的是那种东西吗?

取消引用文字

postcircumfix:< [ ] > 被声明为一堆 multi subs,它们(应该)在它们的左参数上应用 Positional 一致的索引协议。

  • 所有执行 Positional 角色的内置类型。

  • 执行 Positional 角色的用户定义类型 应该 起作用,因为该角色定义类型化的接口存根,这些存根必须由执行角色。

  • 不过ducktyping也可以;如果一个类型实现了接口的基础知识 postcircumfix:< [ ] > 它应该可以工作。

同样的故事适用于 postcircumfix:< { } >postcircumfix:« < > »,但相关的 role/protocol 是 Associative 一致的索引。

类似的情况适用于 postcircumfix:< ( ) >Callable

已经有一些很好的答案了!如果想进一步了解这个一般主题的有趣读物,我可以建议 Day 2 – Perl 6: Sigils, Variables, and Containers 吗?它帮助我理解了一些相关主题,例如作为容器的标量和 decont op <>。我认为这些示例可能会为 $@/% 的相互作用提供更多的基本原理,以按预期有效地管理 packing/unpacking 数据结构的微妙之处。