评估Readonly变量两次时的结果不同

gat*_*eta 7 perl operator-overloading biginteger readonly tie

我注意到,对于使用Readonly模块声明的变量,多次计算变量会产生不同的结果.

>perl -Mbigint -MReadonly -wE "Readonly my $V => 1; foreach (1..2) { say 0 + '1000000000000001' * $V };
1000000000000000
1000000000000001
Run Code Online (Sandbox Code Playgroud)

这是为什么?这似乎是第一次在字符串中解释变量,第二次在数字上下文中.我的猜测是,如果它是数字,Math::BigInteger模块将重载'*'运算符,产生精确的结果.这是Readonly模块中的错误,有什么办法可以避免这种情况吗?

我没有使用perl 5.10和Readonly 1.03 Readonly::XS.

我可以重现那个

  • v5.10.0on MSWin32-x86-multi-thread(ActivePerl)
  • v5.10.0在linux上x86_64-linux-thread-multi.
  • v5.12.0 在Windows上(ActivePerl)

但是,我并没有v5.14.2(ActivePerl).

我也用Readonly 1.04复制了它.我不太确定这是否相关,但Scalar::Util::looks_like_number表现相似:

>perl -MReadonly -MScalar::Util -Mbigint -wE "say $Readonly::VERSION; Readonly my $V => 1; foreach (1..2) { say Scalar::Util::looks_like_number $V; }"
1.04
0
1
Run Code Online (Sandbox Code Playgroud)

gat*_*eta 2

使用 d 变量时似乎存在重载错误,tie该错误已在最新版本的 perl 中修复。以下示例程序显示了差异:

use strict;
use warnings;
use 5.010;

sub TIESCALAR {
  bless {}, 'main';
}

sub FETCH {
  say 'FETCH';
  shift;
}

use overload
  '+' => sub { say 'add called'; },
  '0+' => sub { say 'tonum called'; };

tie my $a, 'main';
my $b = bless {}, 'main';

say "adding non-tied (call $_): ", $b+1 for (1..2);
say "adding tied     (call $_): ", $a+1 for (1..2);
Run Code Online (Sandbox Code Playgroud)

用 Perl 输出v5.10.0

add called
adding non-tied (call 1): 1
add called
adding non-tied (call 2): 1
FETCH
tonum called
adding tied     (call 1): 2
add called
adding tied     (call 2): 1
Run Code Online (Sandbox Code Playgroud)

0+当第一次计算绑定变量时,Perl 尝试在重载运算符之前进行数字转换+,从而产生标准的 Perl 算术。在 perl 版本 >= 5.14 中,输出符合预期:

add called
adding non-tied (call 1): 1
add called
adding non-tied (call 2): 1
FETCH
add called
adding tied     (call 1): 1
FETCH
add called
adding tied     (call 2): 1
Run Code Online (Sandbox Code Playgroud)

perldoc overload

BUGS
....
       Before Perl 5.14, the relation between overloading and tie()ing was
       broken.  Overloading was triggered or not based on the previous
       class of the tie()d variable.

       This happened because the presence of overloading was checked too
       early, before any tie()d access was attempted.  If the class of the
       value FETCH()ed from the tied variable does not change, a simple
       workaround for code that is to run on older Perl versions is to
       access the value (via "() = $foo" or some such) immediately after
       tie()ing, so that after this call the previous class coincides with
       the current one.
Run Code Online (Sandbox Code Playgroud)