To facilitate the transition from one system to another, I'm writing code that aims to be usable as a drop-in replacement to a blessed object, but that internally structures its data differently. This works well, except that there are parts of the code that touch the object's internal data directly.
Unless I want to modify the calling code (which I'd rather avoid) I need to find a way to re-route those assignments to the new internal structure of this class. So I reached for Variable::Magic:
use Test2::V0;
use Variable::Magic qw( cast wizard );
my $wrapper = {
static => { name => "static" },
dynamic => { name => "dynamic" },
};
cast $wrapper->{name}, wizard
get => sub { ${$_[0]} = $wrapper->{dynamic}{name} },
set => sub { $wrapper->{dynamic}{name} = ${$_[0]} };
is $wrapper->{name}, "dynamic";
$wrapper->{name} = "zoom";
is $wrapper->{name}, "zoom";
is $wrapper->{dynamic}{name}, "zoom";
is $wrapper->{static}{name}, "static";
done_testing;
The code above works in that all tests pass. It does what I want it to do. However, it prints the following:
Argument "dynamic" isn't numeric in list assignment at .../Test2/Tools/Compare.pm line 104.
# Seeded srand with seed '20250416' from local date.
ok 1
Argument "zoom" isn't numeric in scalar assignment at -e line 16.
Argument "zoom" isn't numeric in list assignment at .../Test2/Tools/Compare.pm line 104.
ok 2
ok 3
ok 4
1..4
How can I get rid of these warnings? Under normal circumstances I could use no warnings 'numeric'
, but since these warnings are being raised at the call site (see the ones raised in Test2::Tools::Compare
, for example) this is not an option. Or is there a different way to achieve this that doesn't rely on Variable::Magic? An earlier version of this used Sentinel, but that did not survive the assignment.
Bonus points for explaining what is going on here. Why are these warnings being raised? And why are they complaining about a value not being numeric for non-numeric operations (list and scalar assignment)?
The getter and setter is expected to return a integer as per the documentation for Variable::Magic:
The returned value [of the callbacks passed to
set
andget
] is coerced into a signed integer, which is then passed straight to the perl magic API
However, the getter returns whatever value the proxied variable currently has, and the setter returns whatever new value was just given to the proxied variable.
When the getter is called when is
fetches the value of the magic variable, it returns a string that doesn't look like a number. This string is coerced into a signed integer as documented, and this results in a warning (just like int("dynamic")
would).
A similar results occurs when the setter is called when a new value is assigned to the magic variable.
The solution is to have the getter and setter return a number. Any number.
cast $wrapper->{name}, wizard
get => sub { ${$_[0]} = $wrapper->{dynamic}{name}; 0 },
set => sub { $wrapper->{dynamic}{name} = ${$_[0]}; 0 };
This makes the warnings go away while keeping the same behaviour as the original.