Verwirrung über Listen in einem Aggregat, vielleicht Kontextproblem?

8

Rakudo Version 2020.01

Ich habe einen Wegwerfcode geschrieben und mich nicht darum gekümmert, eine Klasse zu implementieren, sondern nur einen Hash als Work-Alike verwendet. Ich fand ein überraschendes Verhalten bei Listen.

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 …)funktioniert wie erwartet, wenn ich anrufe .flatoder .listauf der Liste (obwohl es bereits eine Liste ist‽).

Ich habe versucht, einen minimalen Testfall zu erstellen, aber dies funktioniert identisch AFAICT.

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

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

Ich habe die Dokumentation zu List und Scalar mehrmals gelesen, kann aber aus meiner Beobachtung immer noch keinen Sinn machen. Warum muss ich die Liste im Hash speziell behandeln, aber nicht in der Klasse?

daxim
quelle
Ich bekomme, No such method 'raku' for invocant of type 'Q1'wenn ich versuche, dies auszuführen
Håkon Hægland
2
@ Håkon Hægland: Früher hieß es .perl: Vielleicht ist dein Rakudo nicht aktuell genug?
Elizabeth Mattijsen

Antworten:

10

for führt keine Schleife über Einzelwerte durch.

Wenn Sie etwas in einen skalaren Container legen, wird es aufgelistet.

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

Ein Element in einem Hash ist auch ein skalarer Container.

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

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

Wenn Sie also eine Liste in einen Hash einfügen, wird diese aufgelistet.

my %h;

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

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

Wenn Sie also die Werte durchlaufen möchten, müssen Sie sie auflösen.

%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

Sie können diesen skalaren Container vermeiden, indem Sie stattdessen binden.

%h<list> := list;

(Dies verhindert, dass der =Operator an diesem Hash-Element arbeitet.)


Wenn Sie bemerkt haben, dass Sie es im Klassenobjekt mit einem @Nicht definiert haben$

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

Wenn Sie es in ein geändert $und markiert haben rw, funktioniert es wie im Hash-Beispiel

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)

Es muss eine $Variable sein , sonst befindet es sich nicht in einem Scalar-Container.
Es muss auch markiert rwwerden, damit der Accessor den tatsächlichen Scalar-Container und nicht den de-itemized-Wert zurückgibt.

Brad Gilbert
quelle
6

Das hat nichts mit []Versus zu tun (). Dies hat mit dem Unterschied zwischen $(Angabe eines Elements) und %(Angabe eines Assoziativen) zu tun :

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

Im Fall "b" wird ein Artikel empfangen . Wenn Sie versuchen, darüber zu iterieren, erhalten Sie 1 Iteration für dieses Element. Während Sie im Fall "a" angegeben haben, dass es sich um etwas Assoziatives handelt, das Sie möchten (mit dem %Siegel).

Vielleicht ein klareres Beispiel:

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

Da $aes sich um ein Element handelt, erhalten Sie eine Iteration. Sie können angeben, dass Sie das zugrunde liegende Objekt iterieren möchten, indem Sie Folgendes hinzufügen .list:

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

Oder, wenn Sie mehr Leinen bekommen möchten, stellen Sie ein Präfix ein @:

for @$a { dd $_ }  # 1␤2␤3␤
Elizabeth Mattijsen
quelle
1
Aber in Multi-Sub-Frob iteriere ich nicht über einen Hash oder ein Element, sondern über ein Array oder eine Liste, auf die über das Attribut einer Klasse oder das Hash-Mitglied zugegriffen wird. Ich habe die Typen überprüft und sie an der entsprechenden Stelle mit eingebetteten Kommentaren versehen.
Daxim
5

Nicht unbedingt eine Antwort, sondern eine Beobachtung: In Raku lohnt es sich, im Gegensatz zu Perl Klassen anstelle von Hashes zu verwenden:

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

Verwenden von Klassen und Objekten:

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

Die Verwendung von Klassen ist nicht nur schneller, sondern verhindert auch, dass Sie bei der Initialisierung Tippfehler machen, wenn Sie das is requiredMerkmal hinzufügen :

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.

Und es verhindert, dass Sie beim Zugriff Tippfehler machen:

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
quelle
1
Vielen Dank, dass Sie mir etwas zum Verknüpfen in einem Dokument gegeben haben, das ich schreibe! Ich weiß, dass jemand vor einiger Zeit gesagt hat, dass Klasse + Attribute> Hashes für Geschwindigkeit sind, aber ich konnte es nicht finden. Ich überarbeite derzeit das CLDR-Modul, um Attribute immer dann zu verwenden, wenn eine Hash-Sicherung durchgeführt wird (was dank überraschend überraschend benutzerfreundlich ist FALLBACK)
user0721090601