Different result when evaluating Readonly variable twice
Asked Answered
I

1

7

I noticed that with variables declared with the Readonly module, evaluating a variable multiple times can yield different results.

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

Why is that? It seems like the first time the variable is interpreted in string, the second time in numeric context. My guess is that if it's numeric, the Math::BigInteger module will overload the '*' operator, yielding the exact result. Is this a bug in the Readonly module, and is there any way to avoid that?

I use perl 5.10 and Readonly 1.03 without Readonly::XS.

I can reproduce that with

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

I doesn't happen with v5.14.2 (ActivePerl), however.

I have also reproduced it with Readonly 1.04. I'm not quite sure whether this is related, but Scalar::Util::looks_like_number behaves similarly:

>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
Immediacy answered 17/1, 2014 at 12:5 Comment(6)
Not on v5.16.3 strawberry (1000000000000001 x2)Impervious
Not on v5.14.4 linux 64 bit either, Readonly 1.04.Urinal
Cannot reproduce with Readonly v1.04 on linux using perl v5.10.1 with bigint v0.23, perl v5.18.1 with bigint v0.34, or perl v5.19.8 with bigint v0.36.Gravamen
Seems related: #18199058Immediacy
Can also reproduce it with ActivePerl v5.12.0.Immediacy
@amon: You should be able to reproduce it with perl v5.10.1, see my answer.Immediacy
I
2

Seems to be a bug with overloading when using tied variables that was fixed in more recent versions of perl. The following example program shows the difference:

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);

Output with 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

Perl tries to-number conversion 0+ before the overloaded + operator when first evaluating a tied variable, resulting in standard perl arithmetic. In perl version >= 5.14, the output is as expected:

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

From 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.
Immediacy answered 20/1, 2014 at 12:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.