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

dax*_*xim 8 raku

乐堂版本 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
Run Code Online (Sandbox Code Playgroud)

frob(Hash …)当我打电话.flat.list在列表中时按预期工作(即使它已经是一个列表?)。

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

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

for (Q1.new, Q1.new, Q1.new) -> $elem {
    $elem.raku.say;
}
Run Code Online (Sandbox Code Playgroud)

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

Bra*_*ert 10

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
Run Code Online (Sandbox Code Playgroud)

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

my %h = 'foo' => 'bar';

say %h<foo>.VAR.^name;
# Scalar
Run Code Online (Sandbox Code Playgroud)

因此,如果您将一个列表放入一个 Hash 中,它将被逐项列出。

my %h;

my \list = (1,2,3);
%h<list> = list;

say list.VAR.^name;
# List
say %h<list>.VAR.^name;
# Scalar
Run Code Online (Sandbox Code Playgroud)

因此,如果您想遍历这些值,则必须对其进行逐项列出。

%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
Run Code Online (Sandbox Code Playgroud)

你可以通过绑定来避免这个标量容器。

%h<list> := list;
Run Code Online (Sandbox Code Playgroud)

(这会阻止=操作员处理该散列元素。)


如果你注意到在类对象中你定义了一个@not$

class R1 {
    has Str $.some-str is required;
    has @.some-list is required;
}
Run Code Online (Sandbox Code Playgroud)

如果您将其更改为 an$并标记它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)
Run Code Online (Sandbox Code Playgroud)

它必须是一个$变量,否则它不会在标量容器中。
它还必须被标记,rw以便访问器返回实际的标量容器而不是取消项目的值。


Eli*_*sen 6

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

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)}
Run Code Online (Sandbox Code Playgroud)

在“b”的情况下,收到的是一个item。如果您尝试对其进行迭代,则该项目将获得 1 次迭代。而在“a”的情况下,您已经表明这是您想要的关联(带有%印记)。

也许一个更清楚的例子:

my $a = (1,2,3);
for $a { dd $_ }  # List $a = $(1, 2, 3)?
Run Code Online (Sandbox Code Playgroud)

由于$a是一个项目,您将获得一次迭代。您可以通过添加.list

for $a.list { dd $_ }  # 1?2?3?
Run Code Online (Sandbox Code Playgroud)

或者,如果您想获得更多线噪,请添加前缀 a @

for @$a { dd $_ }  # 1?2?3?
Run Code Online (Sandbox Code Playgroud)


Eli*_*sen 5

严格来说不是答案,而是观察:在 Raku 中,与 Perl 相反,使用类而不是散列是值得的:

my %h = a => 42, b => 666;
for ^10000000 { my $a = %h<a> }
say now - INIT now;  # 0.4434793
Run Code Online (Sandbox Code Playgroud)

使用类和对象:

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
Run Code Online (Sandbox Code Playgroud)

不仅可以更快地使用类,还可以防止您在添加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.
Run Code Online (Sandbox Code Playgroud)

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

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