Как передать хеш-подобный объект, который выполняет ассоциативную роль в конструкторе, ожидающем хеш? - PullRequest
6 голосов
/ 07 апреля 2019

Я экспериментирую с настроенными хешами . Следующее пытается реализовать более простой поиск для хэшей, подобных конфигурации:

use v6;

class X::Config::KeyNotFound is Exception {
    method message() {
        "Key not found!";
    }
}

# A hash that allows for nested lookup using a '.' to separate keys.
# (This means that keys themselves cannot contain a dot)
# For example:
#
#   %h = Config.new(%(a => %(b => 1)));
#   my $foo = %h<a.b>;  # <-- $foo = 1
#
class Config does Associative[Cool,Str] {
    has %.hash;

    multi method AT-KEY ( ::?CLASS:D: $key) {
        my @keys = $key.split('.');
        my $value = %!hash;
        for @keys -> $key {
            if $value{$key}:exists {
                $value = $value{$key};
            }
            else {
                X::Config::KeyNotFound.new.throw;
            }
        }
        $value;
    }

    multi method EXISTS-KEY (::?CLASS:D: $key) {
        my @keys = $key.split('.');
        my $value = %!hash;
        for @keys -> $key {
            if $value{$key}:exists {
                $value = $value{$key};
            }
            else {
                return False;
            }
        }
        return True;
    }

    multi method DELETE-KEY (::?CLASS:D: $key) {
        X::Assignment::RO.new.throw;
    }

    multi method ASSIGN-KEY (::?CLASS:D: $key, $new) {
        X::Assignment::RO.new.throw;
    }

    multi method BIND-KEY (::?CLASS:D: $key, $new){
        X::Assignment::RO.new.throw;
    }
}

my %hash = a => %(aa => 2, ab => 3), b => 4;
my %cfg := Config.new( hash => %hash );

# A dummy class to illustrate the problem:    
class MyTest {
    has %.config;
}

# Now this code does not work:
MyTest.new(
    config  => %cfg,
);

Вывод:

Odd number of elements found where hash initializer expected:
Only saw: Config.new(hash => {:a(${:aa(2), :ab(3)}), :b(4)})
  in block <unit> at ./p.p6 line 70

(строка 70 - это строка MyTest.new()

Код работает нормально, если вместо этого я передаю обычный конструктор хешу, например, используя %hash вместо %cfg:

MyTest.new(
    config  => %hash,
);

1 Ответ

8 голосов
/ 07 апреля 2019

Класс также должен выполнять роль Iterable:

class Config does Associative[Cool,Str] does Iterable {
    ...
}

Что требует реализации метода iterator. В этом случае, вероятно, проще всего делегировать итератору вложенного хэша:

method iterator() { %!hash.iterator }

С этим ошибка устранена. (По умолчанию iterator дает итератор, который представляет собой последовательность из 1 элемента, содержащую сам объект, таким образом, наблюдается ошибка.)

Итератор необходим, потому что семантика построения объекта с хеш-атрибутом присваивание , не привязка. Когда мы присваиваем хэш, мы получаем Iterator из того, что мы присваиваем, и повторяем его, чтобы получить значения для присваивания. Я упоминаю об этом, если ваше ожидание было обязательным, то есть MyTest будет ссылаться на экземпляр Config. Для этого нужно написать пользовательский BUILD, который выполняет связывание в MyTest, или вместо этого объявить его как has $.config, что означает, что он будет просто ссылаться на экземпляр Config, а не копировать значения из него в новый хеш.

...