confusión sobre las listas contenidas en un agregado, ¿tal vez un problema de contexto?

8

Rakudo versión 2020.01

Estaba escribiendo un código desechable y no me molesté en implementar una clase, solo usé un Hash como trabajo. Encontré un comportamiento sorprendente con las 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 se espera cuando llamo .flato .listen la lista (aunque ya es una lista‽).

Intenté hacer un caso de prueba mínimo, pero esto funciona 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;
}

He leído la documentación en List y Scalar varias veces, pero todavía no puedo entender mi observación. ¿Por qué tengo que tratar especialmente la lista en Hash, pero no en la clase?

daxim
fuente
Me sale No such method 'raku' for invocant of type 'Q1'cuando intento ejecutar esto
Håkon Hægland
2
@ Håkon Hægland: solía llamarse .perl: ¿quizás su Rakudo no es lo suficientemente actual?
Elizabeth Mattijsen

Respuestas:

10

for no recorre los valores detallados.

Cuando colocas algo en un contenedor escalar, se detalla.

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

Un elemento en un hash también es un contenedor escalar.

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

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

Entonces, si coloca una lista en un Hash, se detallará.

my %h;

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

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

Entonces, si desea recorrer los valores, debe des-detallarlo.

%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

Puede evitar este contenedor escalar mediante el enlace en su lugar.

%h<list> := list;

(Esto evita que el =operador trabaje en ese elemento hash).


Si notó que en el objeto de clase lo definió con un @no$

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

Si lo cambió a $y lo marca rw, funcionará como el ejemplo de 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)

Tiene que ser una $variable o no estará en un contenedor escalar.
También debe marcarse rwpara que el descriptor de acceso devuelva el contenedor Scalar real en lugar del valor des-detallado.

Brad Gilbert
fuente
6

Esto no tiene nada que ver con []versus (). Esto tiene que ver con la diferencia entre $(que indica un elemento) y %(que indica un asociativo):

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

En el caso "b", lo que se recibe es un artículo . Si intenta iterar sobre eso, obtendrá 1 iteración, para ese elemento. Mientras que en el caso "a", ha indicado que es algo asociativo que desea (con el %sigilo).

Quizás un ejemplo más claro:

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

Como $aes un elemento, obtienes una iteración. Puede indicar que desea iterar sobre lo subyacente, agregando .list:

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

O, si desea obtener más linenoisy, prefije a @:

for @$a { dd $_ }  # 1␤2␤3␤
Elizabeth Mattijsen
fuente
1
Pero en multi sub frob, no estoy iterando sobre un hash o elemento, estoy iterando sobre una matriz o lista a la que accede el atributo de una clase o el miembro hash. He verificado los tipos y los he anotado en el lugar relevante con comentarios en línea.
daxim
5

No es estrictamente una respuesta, sino una observación: en Raku, vale la pena usar clases en lugar de hashes, al contrario de Perl:

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

Usando clases y 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

El uso de clases no solo es más rápido, sino que también evita que realice errores tipográficos en la inicialización si agrega el is requiredrasgo:

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.

Y le impide hacer errores tipográficos al acceder a él:

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
fuente
1
¡Gracias por darme algo para vincular en un documento que estoy escribiendo! Sé que alguien había dicho que clase + atributos> hashes para velocidad hace un tiempo, pero no pude encontrarlo. Actualmente estoy reelaborando el módulo CLDR para usar atributos siempre que sea con una copia de seguridad hash (que es sorprendentemente fácil de usar gracias a FALLBACK)
user0721090601