【问题标题】:What's the benefit of assigning non-scalars to scalars?将非标量分配给标量有什么好处?
【发布时间】:2020-06-01 20:42:00
【问题描述】:

我有时会看到(对我而言)在变量前面使用错误符号的代码

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

Q1:使用标量容器而不是仅仅使用“正确”容器有什么好处?

我知道 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 中是有意义的,因为它们服务于两个截然不同的目的。

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

【问题讨论】:

  • “为了扩展和澄清我在这里的意思,基本上有两种定义事物的方法,按值和按引用” 从汇编程序开始,所有编程不都是这样吗?它是直接的或间接的。在机器级别,您要么谈论一些位,要么谈论一些位,这些位是包含一些位的内存位置。在中级,它是指针。在高层次上,它是一种价值或参考。不是所有的编程语言都一样吗,即使他们喜欢使用不同的术语,用不同的类型包装,等等?
  • @raiph 当然,但这是关于语言结构的。 Perl 系列语言也可以隐藏所有这些信息,并使其对用户完全不透明(如 Python),其中引用和值的访问和使用彼此没有区别。但是 Perl 5 明确区分了两者,在我看来,这种旧语法的痕迹仍然存在于它的姊妹语言中,但没有相同的底层语义。
  • Imo Raku 使这两者之间的区别非常更清晰,但也使得在区别无关紧要的情况下更容易消除区别或妨碍。我认为 Perl 设计在这方面相对于 Python 有一些优点和缺点。 Imo Raku通常在消除缺点的同时保留优点。此外,在同时使用 Raku 和 Perl 的人中(显然是一群自选的人,他们认为在某些事情上使用 Raku 而不是 Perl 有价值),清理这方面通常被认为是一种优势。 YMMV。
  • 我不知道,我还是 Perls 的新手。这就是为什么我要问所有愚蠢的问题:P
  • 很明显,我很喜欢你的问题,并认为它们是好问题。 (也就是说,我仍然对棘轮错误的影响感到震惊。我的左脑一直认为除了纯数学驱动的编程方法之外的任何东西,从上到下,都是自找麻烦,有偏差就抱怨。同时我的右脑很喜欢乐,但担心烤和烤的优缺点之间的平衡cheese。)

标签: raku sigils


【解决方案1】:

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 一个引用。 [...] 在术语(名词)位置 isArray 参考文字。很少或不需要使用标量变量来显式存储引用。

  • 前几行中的括号;

  • 在作为最后一行代码的大多数右大括号之后的分号;

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

为什么要使用印记?

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

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

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

  • &amp;foo 类型检查绑定或分配的值是否具有Callable 角色。

  • @foo 告诉 Raku 选择 Iterable 行为。还要键入检查绑定值是否具有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 &amp;,了解为什么它是 func.(...) 而不仅仅是 func(...)。最后一个 nosigil 案例没有什么影响,因为在 Raku 中通常会这样写:

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

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

(更多讨论请参见Is there a purpose or benefit in prohibiting sigilless variables from rebinding?。)

$foo 而不是 @foo?

乐乐支持:

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

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

  • 无限的惰性列表。 (另一件事可能非常有用。)

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

假设infinite 是一个无限惰性列表,它为.is-lazy 返回False

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= 之类的元操作绑定调用标量语义,即不要啜饮。)

我们在这里看到了另一个不同之处;分配给$ 变量将保留ListList,但是分配给@ 变量“slurps”它的值进入 绑定@ 变量的任何容器到(默认情况下,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 而不是 &amp;foo?

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

与其他 sigil 替代品一样,主要区别在于您是否愿意强调可调用对象的 Callable 特性。

作为设置,请注意 Raku 中的 sub 声明声明了一个带有 &amp; sigil 的相应常量标识符:

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

这意味着如果您使用&amp; 印记声明一个可变的例程变量,您可以在没有印记的情况下将其称为

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:我们是在再次处理类似 Perl 5 的引用文字,还是有其他东西在起作用?

尽管有你的例子,知道 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:&lt; [ ] &gt; 被声明为一堆(应该)在其左参数上应用Positional 一致索引协议的多子。

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

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

  • 但是ducktyping也可以;如果一个类型实现了接口postcircumfix:&lt; [ ] &gt; 的基础知识,它应该可以工作。

同样的情况也适用于postcircumfix:&lt; { } &gt;postcircumfix:« &lt; &gt; »,但相关的角色/协议是Associative 一致的索引。

类似的情况也适用于postcircumfix:&lt; ( ) &gt;Callable

【讨论】:

  • 第二个 for 循环终止:my @a=(1,2),Any,(5,6); for @a -&gt; $b {say $b}; for @a -&gt; @b {say @b}; say "Hello";
  • 不,我说的不是后环运算符,而是环运算符。在 Perl5 中,(1, 2, 3)[1, 2, 3] 之间的主要区别在于第一个是数组,第二个是对数组的引用@arr = (1, 2, 3); vs $aref = [1, 2, 3] 因为引用是标量的。与哈希相同:%hash = ('a' =&gt; 1, 'b' =&gt; 2); vs $href = {'a' =&gt; 1, 'b' =&gt; 2}; $aref$href,是标量,需要取消引用才能使用。
  • ElectricCoffee:在 Raku 中,( ) 只是一种分组语法,因此 (3) 不是一个列表。创建列表的是链式中缀 listop ,(如果您想要单个项目列表,则使用 (3,) 甚至只是 3,,或者您可以将 3 分配给 @-sigiled var)。有一个环绕运算符[ ] 创建了一个数组——它恰好是从, 创建的列表中构造的。
  • @raiph Perl 在符号方面的最大区别是((1, 2), (3, 4)) 产生(1, 2, 3, 4)([1, 2], [3, 4]) 不是由于[] 的标量性质与非标量性质().
  • Raku 支持多种方式,而不是只有一种“啜饮”(展平)方式。它可以模仿 Perl 的方式,将((1, 2), (3, 4))([1,2],[3,4]) 分别变成(1, 2, 3, 4)[1,2],[3,4],或者在两种 的情况下保留结构,将它们变成(1,2),(3,4)[1,2],[3,4]
【解决方案2】:

已经有一些很棒的答案了!对于这个一般主题的更有趣的阅读,我可以建议Day 2 – Perl 6: Sigils, Variables, and Containers 吗?它帮助我理解了一些相关主题,例如作为容器的标量和 decont op &lt;&gt;。我认为这些示例可能会为 $@/% 的相互作用提供更多理由,以管理按预期有效打包/解包数据结构的微妙之处。

【讨论】:

  • 老实说,这是一个很棒的页面!它清除了很多东西!
猜你喜欢
  • 2015-04-12
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-07
  • 1970-01-01
  • 2016-02-10
  • 2022-10-05
相关资源
最近更新 更多