Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Question About Hash Binding in EVAL in Raku

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!

like image 241
JustThisGuy Avatar asked Feb 18 '21 21:02

JustThisGuy


1 Answers

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.

Solution

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 EVALd, 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")})

Explanation of the surprises

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.

like image 61
raiph Avatar answered Nov 11 '22 04:11

raiph