confusão sobre listas contidas em um problema agregado, talvez de contexto?

8

Versão Rakudo 2020.01

Eu estava escrevendo um código descartável e não me incomodei em implementar uma classe, apenas usei um Hash como trabalho. Eu encontrei um comportamento surpreendente com listas.

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

frob(Hash …)funciona como esperado quando ligo .flatou .listna lista (mesmo que já seja uma lista‽).

Tentei criar um caso de teste mínimo, mas isso funciona com o AFAICT idêntico.

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

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

Eu li a documentação em List e Scalar várias vezes, mas ainda não consigo entender minha observação. Por que preciso tratar a lista no Hash, mas não na classe?

daxim
fonte
Recebo No such method 'raku' for invocant of type 'Q1'quando tento executar isso
Håkon Hægland
2
@ Håkon Hægland: costumava ser chamado .perl: talvez o seu Rakudo não seja atual o suficiente?
Elizabeth Mattijsen

Respostas:

10

for não faz loop sobre valores discriminados.

Quando você coloca algo em um contêiner escalar, ele é discriminado.

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

Um elemento em um Hash também é um contêiner escalar.

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

say %h<foo>.VAR.^name;
# Scalar

Portanto, se você colocar uma lista em um Hash, ele será detalhado.

my %h;

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

say list.VAR.^name;
# List
say %h<list>.VAR.^name;
# Scalar

Portanto, se você quiser fazer um loop sobre os valores, precisará removê-lo.

%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

Você pode evitar esse contêiner escalar vinculando.

%h<list> := list;

(Isso impede que o =operador trabalhe nesse elemento hash.)


Se você notou que no objeto de classe você o definiu com um @não$

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

Se você o mudou para um $e o marcou, rwele funcionará como o exemplo Hash

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)

Tem que ser uma $variável ou não estará em um contêiner Scalar.
Ele também deve ser marcado rwpara que o acessador retorne o contêiner escalar real, em vez do valor não discriminado.

Brad Gilbert
fonte
6

Isso não tem nada a ver com []versus (). Isso tem a ver com a diferença entre $(indicando um item) e %(indicando um associativo):

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)}

No caso "b", o que é recebido é um item . Se você tentar repetir isso, receberá 1 iteração para esse item. Enquanto no caso "a", você indicou que é algo associativo que você deseja (com o %sigilo).

Talvez um exemplo mais claro:

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

Como $aé um item, você obtém uma iteração. Você pode indicar que deseja iterar na coisa subjacente, adicionando .list:

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

Ou, se você quiser obter mais linenoisy, prefixe a @:

for @$a { dd $_ }  # 1␤2␤3␤
Elizabeth Mattijsen
fonte
1
Mas no multi sub frob, não estou repetindo um hash ou item, mas repetindo uma matriz ou lista acessada pelo atributo de uma classe ou membro do hash. Eu verifiquei os tipos e os anotei no local relevante com comentários embutidos.
daxim 04/02
5

Não estritamente uma resposta, mas uma observação: em Raku, vale a pena usar classes em vez de hashes, ao contrário de Perl:

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

Usando classes e objetos:

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

O uso de classes não só é mais rápido, como também impede que você cometa erros de digitação na inicialização, se você adicionar a is requiredcaracterística:

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.

E impede que você faça erros de digitação ao acessá-lo:

my $a = A.new(a => 42, b => 666);
$a.bb;
# No such method 'bb' for invocant of type 'A'. Did you mean 'b'?
Elizabeth Mattijsen
fonte
1
Obrigado por me dar algo para vincular em um documento que estou escrevendo! Eu sei que alguém disse que classe + atributos> hashes para velocidade há um tempo atrás, mas eu não consegui encontrá-lo. No momento, estou reformulando o módulo CLDR para usar atributos sempre que houver um backup de hash (que é surpreendentemente fácil de usar graças a FALLBACK)
user0721090601