问题
I am experimenting with customized hashes. The following is trying to implement a simpler lookup for config-like hashes:
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,
);
The output is:
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
(Line 70 is the line MyTest.new(
)
The code works fine if I pass a normal hash to the constructor instead, for example using %hash
instead of %cfg
:
MyTest.new(
config => %hash,
);
回答1:
The class also needs to do the Iterable
role:
class Config does Associative[Cool,Str] does Iterable {
...
}
Which requires that an iterator
method be implemented. In this case, it's probably easiest to delegate to the iterator of the nested hash:
method iterator() { %!hash.iterator }
With this, the error is resolved. (The default iterator
gives an iterator that is a 1-item sequence containing the object itself, thus the error observed.)
The iterator is required because the semantics of constructing an object with a hash attribute are assignment, not binding. When we assign into a hash, then we obtain an Iterator
from the thing we're assigning from, and iterate it to get the values to assign. I mention this in case your expectation was binding - that is, that MyTest
will reference the instance of Config
. For that, one would need to write a custom BUILD
that does binding in MyTest
, or to declare it as has $.config
instead, which means it will just reference the Config
instance rather than copying the values out of it into a new hash.
来源:https://stackoverflow.com/questions/55557297/how-to-pass-hash-like-object-which-does-associative-role-to-a-constructor-expect