путаница в списках, содержащихся в совокупности, может быть, проблема контекста? - PullRequest
8 голосов
/ 04 февраля 2020

Rakudo версия 2020.01

Я писал какой-то одноразовый код и не удосужился реализовать класс, просто использовал Ha sh в качестве рабочего. Я обнаружил удивительное поведение со списками.

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 …) работает, как и ожидалось, когда я вызываю .flat или .list в списке (даже если это уже список a).

Я пытался создать минимальный тестовый пример, но он работает идентично AFAICT.

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

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

Я несколько раз читал документацию по List и Scalar, но все еще не могу сделать смысл вне моего наблюдения. Почему я должен специально относиться к списку в Ха sh, а не в классе?

Ответы [ 3 ]

10 голосов
/ 04 февраля 2020

for не l oop сверх детализированных значений.

Когда вы помещаете что-то в скалярный контейнер, оно детализируется.

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

Элемент в Ха sh также является скалярным контейнером.

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

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

Так что если вы поместите список в Ха sh, он будет детализирован.

my %h;

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

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

Поэтому, если вы хотите, чтобы l oop превышало значения, вы должны удалить его из списка.

%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;

(Это не позволяет оператору = работать с этим элементом ha sh.)


Если вы заметили, что в объекте класса вы определили его с @ not $

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

Если вы изменили его на $ и отметили его rw, он будет работать как пример Ha sh

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, чтобы метод доступа возвращал реальный скалярный контейнер, а не детализированное значение.

6 голосов
/ 04 февраля 2020

Это не имеет ничего общего с [] против (). Это связано с разницей между $ (с указанием предмета) и % (с указанием ассоциативности):

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 итерацию для этого элемента. В то время как в случае «а» вы указали, что это что-то ассоциативное, которое вы хотите (с % сигилой).

Возможно, более ясный пример:

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

, поскольку $a это элемент, вы получаете одну итерацию. Вы можете указать, что вы хотите выполнить итерацию для базовой вещи, добавив .list:

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

или, если вы хотите получить больше ленуаиса, префикс a @:

for @$a { dd $_ }  # 1␤2␤3␤
5 голосов
/ 04 февраля 2020

Не только ответ, но и наблюдение: в Раку платят за использование классов, а не хешей, в отличие от 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'?
...