对聚合中包含的列表感到困惑,也许是上下文问题?



乐道版本 2020.01

我正在编写一些一次性代码,并没有费心去实现一个类,只是使用一个 Hash 作为类似工作。我发现列表有一些令人惊讶的行为。

class Q1 {}
class R1 {
has Str $.some-str is required;
has @.some-list is required;
}
my $r1 = R1.new(
some-str => '…',
some-list => (Q1.new, Q1.new, Q1.new)
);
# hash as poor man's class
my $r2 = {
some-str => '…',
some-list => (Q1.new, Q1.new, Q1.new)
};
multi sub frob(R1 $r1) {
for #`(Array) $r1.some-list -> $elem {
$elem.raku.say;
}
}
multi sub frob(Hash $r2) {
for #`(List) $r2<some-list> -> $elem {
$elem.raku.say;
}
}
frob $r1;
# OK.
# Q1.new
# Q1.new
# Q1.new
frob $r2;
# got:
# (Q1.new, Q1.new, Q1.new)
# expected:
# Q1.new
# Q1.new
# Q1.new

当我调用列表中的.flat.list时,frob(Hash …)按预期工作(即使它已经是一个列表‽(。

我试图制作一个最小的测试用例,但这工作原理相同。

for [Q1.new, Q1.new, Q1.new] -> $elem {
$elem.raku.say;
}
for (Q1.new, Q1.new, Q1.new) -> $elem {
$elem.raku.say;
}

我已经多次阅读了有关List和Scalar的文档,但是我仍然无法理解我的观察。为什么我必须特殊对待哈希中的列表,而不是类中的列表?

for

不会循环逐项列出的值。

当您将某些东西放入标量容器中时,它会逐项列出。

sub foo ( $v ) { # itemized
for $v { .say }
}
sub bar ( v ) {
for v { .say }
}
foo (1,2,3);
# (1 2 3)
bar (1,2,3);
# 1
# 2
# 3

哈希中的元素也是一个标量容器。

my %h = 'foo' => 'bar';
say %h<foo>.VAR.^name;
# Scalar

因此,如果您将列表放入哈希中,它将被逐项列出。

my %h;
my list = (1,2,3);
%h<list> = list;
say list.VAR.^name;
# List
say %h<list>.VAR.^name;
# Scalar

因此,如果要遍历值,则必须将其逐项列出。

%h<list>[]
%h<list><>
%h<list>.list
%h<list>.self
@(%h<list>)
given %h<list> -> @list { … }
my @list := %h<list>;
(my @ := %h<list>)  # inline version of previous example

可以通过绑定来避免此标量容器。

%h<list> := list;

(这会阻止=运算符处理该哈希元素。


如果您注意到在类对象中,您用@定义它,则不$

class R1 {
has Str $.some-str is required;
has @.some-list is required;
}

如果您将其更改为$并将其标记为rw它将像哈希示例一样工作

class R2 {
has Str $.some-str is required;
has List $.some-list is required is rw;
}
my $r2 = R2.new(
some-str => '…',
some-list => (1,2,3),
);
for $r2.some-list { .say }
# (1 2 3)

它必须是$变量,否则它不会在标量容器中。
还必须将其标记为rw以便访问器返回实际的 Scalar 容器,而不是逐项化的值。

这与[]()无关。 这与$(指示项目(和%(指示关联(之间的区别有关:

sub a(%h) { dd %h }       # a sub taking an Associative
sub b(Hash $h) { dd $h }  # a sub taking an item of type Hash
a { a => 42 };  # Hash % = {:a(42)}
b { a => 42 };  # ${:a(42)}

在"b"的情况下,收到的是一个项目。 如果您尝试迭代它,您将获得该项目的 1 次迭代。 而在"a"的情况下,你已经表明它是你想要的联想的东西(带有%符号(。

也许是一个更清晰的例子:

my $a = (1,2,3);
for $a { dd $_ }  # List $a = $(1, 2, 3)␤

由于$a是一个项目,因此您将获得一次迭代。 您可以通过添加.list来指示要迭代底层事物:

for $a.list { dd $_ }  # 1␤2␤3␤

或者,如果您想获得更多线路噪声,请在前面加上@

for @$a { dd $_ }  # 1␤2␤3␤

严格来说不是一个答案,而是一个观察:在 Raku 中,使用类而不是哈希是值得的,这与 Perl 相反:

my %h = a => 42, b => 666;
for ^10000000 { my $a = %h<a> }
say now - INIT now;  # 0.4434793

使用类和对象:

class A { has $.a; has $.b }
my $h = A.new(a => 42, b => 666);
for ^10000000 { my $a = $h.a }
say now - INIT now;  # 0.368659

使用类不仅速度更快,而且如果您添加is required特征,还可以防止您在初始化中出现拼写错误:

class A { has $.a is required; has $.b is required }
A.new(a => 42, B => 666);
# The attribute '$!b' is required, but you did not provide a value for it.

它可以防止您在访问它时输入拼写错误:

my $a = A.new(a => 42, b => 666);
$a.bb;
# No such method 'bb' for invocant of type 'A'. Did you mean 'b'?

相关内容

最新更新