I've run into something I don't understand with binding a hash in an EVAL. Binding the hash outside of EVAL works as expected. An unbound hash in an EVAL works as expected. But binding a hash inside of an EVAL doesn't work as I would expect. (My expectations may be wrong.) Here's the code:
This works:
#!/usr/bin/env raku
class Hash::Test does Associative {
has %.hash;
multi method STORE(@pairs) {
for @pairs -> $pair {
self.STORE: $pair
}
}
multi method STORE(Pair $pair) {
%!hash{$pair.key} = $pair.value;
}
}
no strict;
%hash-test := Hash::Test.new;
%hash-test = foo => 'bar', baz => 'quux';
say %hash-test;
Output:
$ ./hash-binding-works.raku
Hash::Test.new(hash => {:baz("quux"), :foo("bar")})
And this works:
#!/usr/bin/env raku
class Foo {
use MONKEY-SEE-NO-EVAL;
method eval(Str $code) {
EVAL $code;
}
}
my $code = q:to/END/;
no strict;
%hash = foo => 'bar', baz => 'quux';
END
Foo.eval: $code;
say %Foo::hash;
Output:
$ ./hash-EVAL-works.raku
{baz => quux, foo => bar}
But this does not work:
#!/usr/bin/env raku
class Hash::Test does Associative {
has %.hash;
multi method STORE(@pairs) {
for @pairs -> $pair {
self.STORE: $pair
}
}
multi method STORE(Pair $pair) {
%!hash{$pair.key} = $pair.value;
}
}
class Foo {
use MONKEY-SEE-NO-EVAL;
method eval(Str $code) {
EVAL $code;
}
}
my $code = q:to/END/;
no strict;
%hash-test := Hash::Test.new;
%hash-test = foo => 'bar', baz => 'quux';
say %hash-test;
END
no strict;
Foo.eval: $code;
say %Foo::hash-test;
Output:
$ ./hash-EVAL-does-not-work.raku
Hash::Test.new(hash => {:baz("quux"), :foo("bar")})
{}
Hash::Test is not the real class I am using, but is what I golfed it down to. Can anyone explain what's going on here? Thanks!
TL;DR no strict;
autodeclares package variables via an implicit our
declarator. our
declares package variables via an implicit my
lexical variable declarator that binds to an implicit package symbol with the same name. Your code breaks that binding, and that breaks your code. To fix it, say the same thing another way.
no strict;
doesn't help so we get rid of that. Same goes for our
. Instead we declare a my
lexical variable, do everything we need/can do with that, and then, at the end of the code that will be EVAL
d, create a package variable and bind it to the value stored in the lexical.
my $code = q:to/END/;
my %hash is Hash::Test;
%hash = foo => 'bar', baz => 'quux';
OUR::<%hash-test> := %hash;
END
Foo.eval: $code;
say %Foo::hash-test; # Hash::Test.new(hash => {:baz("quux"), :foo("bar")})
Variables declared without an explicit declarator under no strict;
implicitly declare our
variables:
no strict;
%hash-test = :a;
say MY::<%hash-test>; # {a => True}
say OUR::<%hash-test>; # {a => True}
In other words, the net effect of just the first two lines above are equivalent to:
our %hash-test = :a;
In turn, our
variables implicitly declare my
variables and follow the logic shown in this SO. So this code:
no script;
%hash-test := ...;
Is doing this:
(my %hash-test := $?PACKAGE.WHO<%hash-test>) := ...;
which creates a lexical %hash-test
symbol, and a package %hash-test
symbol, and binds them -- which binding is essential to proper functioning of our
variables -- and then immediately breaks that essential binding.
Thereafter, whatever the rest of your code does, it does it to just the lexical %hash-test
version of the variable, leaving the package symbol version of %hash-test
high and dry so that it just later autovivifies to an empty hash.
As jnthn says in a comment below the SO I linked at the start:
We certainly could warn that binding to an
our
variable is pointless
But there is currently no warning.
As you explain in your comment below, when you tried to use %hash-test is Hash::Test
the compiler mysteriously decides that you've written "two terms in a row". As I explain in my comment, that's due to the above chicanery when you declare an our
variable using the usual syntax (or implicitly by using no strict;
).
To fix all of the above, forget no strict;
, forget use of our
, and instead:
Use a lexical to do the work setting up the value;
Wrap up by creating the package symbol with OUR::<%hash-test>
and binding that to the value of the lexical.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With