http://qs321.pair.com?node_id=11147932

syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi,
I've just been bitten by this annoying behaviour (using perls 5.30.0, 5.36.0, and probably others):
>perl -MMath::BigInt -wle "$bi = Math::BigInt->new(10); $x = $bi; $y = + $bi; $x->binc(); print $x; print $y; print $bi;" 11 11 11
As you can see, in incrementing $x, both $y and $bi have also been incremented.
This behaviour can be avoided if I use the overloaded ++ operator instead of the binc() method call:
>perl -MMath::BigInt -wle "$bi = Math::BigInt->new(10); $x = $bi; $y = + $bi; $x++; print $x; print $y; print $bi;" 11 10 10
Of course, if I want to call a method for which there is no overloaded operation, then I have to workaround it some other way. It's probably not such a big deal, but there's no warning about the danger, and it can lead to some unexpected and puzzling results if you absent-mindedly fall into the trap.

I think I've struck it before ... something to do with the wonderful Copy-On-Write, IIRC.
Is there some way (apart from building perl without COW) to guard against getting bitten by this ?
Is it a bug in perl ?
Is it a bug in Math::BigInt ?
Whatever it is, it certainly doesn't DWIM.

Maybe it's just that this machine is one o' them quantum computers ;-)

Cheers,
Rob

Replies are listed 'Best First'.
Re: Action at a distance
by hv (Prior) on Nov 03, 2022 at 12:07 UTC

    This is deliberate behaviour of Math::BigInt: they are objects, and $x = $y just copies the reference. See the section on "Modifying and =" under CAVEATS. You will see also that binc() is documented in a section Arithmetic methods which is headed "These methods modify the invocand object and returns it" (sic).

    Generally the module is designed such that if you treat the objects as numbers it will attempt to do what is needed to preserve that illusion; if you treat them as objects, by calling methods on them, you need to read rather more of the docs.

Re: Action at a distance
by haj (Vicar) on Nov 03, 2022 at 15:50 UTC
    Whatever it is, it certainly doesn't DWIM.

    Yeah, it is a pitfall... which is common to all objects which try to behave like they are primitive data types. These are like photons, which behave either as waves or as particles, depending on how you look at them. No wonder that they show quantum effects.

    My favourite workaround for this is to write $x = $bi * 1;. This is like $x = $b1->copy but also works if $b1 is just a plain integer or a Math::Complex object (these does not offer a copy method) or even a Math::Matrix or Math::Quaternion object.

      My favourite workaround for this is to write $x = $bi * 1;

      Yes, that works fine ... for those who:
      1) are aware that it's needed;
      and
      2) remember to do it.
      I'd like a better solution, but I don't yet see that one exists.

      You can also work around the problem by doing $x = Math::BigInt->new($bi), but the brevity of doing $x = $bi is a very attractive alternative ... or, at least, would be if it DWIMmed.
      Doing $x = $bi will not call the sub that overloads '=' until an attempted modification of the value of $x has been detected and, while doing $x++ triggers that detection, doing $x->binc() apparently does not.

      Thanks for the thoughts so far.
      Is there something already posted in this thread to which I should pay more attention ?

      Cheers,
      Rob
Re: Action at a distance (updated)
by LanX (Saint) on Nov 03, 2022 at 11:35 UTC
    I'm not an expert on Math::BigInt, but it seems to create objects and by copying you are always accessing the same instance.

    And when searching for a clone method I found this in the documentation

    $y = $x->copy();         # make a copy (unlike $y = $x)

    So maybe try this?

    HTH! :)

    update

    > This behaviour can be avoided if I use the overloaded ++ operator instead of the binc() method call:

    Well you could "overload" assignments via tie, to always do a copy ...

    see overload#Overloadable Operations:

    > Simple assignment is not overloadable (the = key is used for the "Copy Constructor"). Perl does have a way to make assignments to an object do whatever you want, but this involves using tie(), not overload - see "tie" in perlfunc and the "COOKBOOK" examples below.

    Cheers Rolf
    (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
    Wikisyntax for the Monastery

      well you could "overload" assignments via tie to always do a copy

      Yes - that might be what I'm after. I'm looking for a solution that would be applied inside Math::BigInt.
      I'll see if I can get my head around it. (Thanks for the link.)

      I actually struck the problem in Math::MPFR (which is where I'd like to fix it), but used Math::BigInt as the demo because everyone already has that module and I believe that it's the same issue with both of those modules.

      Cheers,
      Rob

      I wouldn't go with tie (if it even works?). I'd overload = to automatically clone the object on assignment.

      Another approach is to use immutable objects. Such a class wouldn't provide a method for incrementing the object; it would provide a method that returns a new object with the higher value.

      Both of these approaches add the unnecessary computational and memory overhead of creating clones in situations where they aren't needed.

        > I'd overload = to automatically clone the object on assignment.

        Sorry, that's too easy to misunderstand.

        Let me be more precise:

        $b = $a with = overloaded to ->clone will not do an immediate $b =  $a->clone °

        It's rather a kind of copy-on-write.

        After the assignment the refs will still be identical : $b == $a

        The ->clone ("copy" in COW) will only happen delayed just prior to changing $b or $a ("write" in COW)

        But the wording is fuzzy and needs to be tested.

        Anyway this could indeed fix the problem of the OP in an efficient way.

        Cheers Rolf
        (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
        Wikisyntax for the Monastery

        °) In Math::BigInt the cloning is done with the method ->copy

      You can overload = to get the desired effect. But that only works if you have immutable objects.

        > You can overload = to get the desired effect.

        Not sure what you mean, the reason why I've put "overload" in double-quotes is that it's not possible to change° assignment via overload.

        One needs to tie the obj-ref too, see the linked doc.

        Cheers Rolf
        (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
        Wikisyntax for the Monastery

        update

        °) well, not the immediate assignment, see Re^3: Action at a distance

Re: Action at a distance
by afoken (Chancellor) on Nov 03, 2022 at 15:36 UTC

    Overloading '=' was recently listed in Selected Best Nodes, it seems to explain the behaviour.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
      Thanks, at the end it says:

      > The documentation for overload is confusing and oddly ordered. I hope to work on that soon.

      I wonder what happened ;-)

      Cheers Rolf
      (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
      Wikisyntax for the Monastery

Re: Action at a distance
by ikegami (Patriarch) on Nov 03, 2022 at 14:41 UTC

    I think I've struck it before ... something to do with the wonderful Copy-On-Write, IIRC.

    Quite the opposite. If anything, it's about the lack of copy on write.

    $x and $y are references to objects. You are copying the reference, but not the referenced object (on write or otherwise).

    You want $y = $x->copy(); to create a clone.

    Is there some way (apart from building perl without COW) to guard against getting bitten by this ?

    To avoid getting bit by this, only uses classes that provide immutable objects.

    Also, you'll need to avoid references to arrays and hashes, as the same issue is found there.

    my $x = [ 4 ]; my $y = $x ++$_ for @x; say @y; # 5!!

    Even then, you'll have to worry about aliases.

    Is it a bug in perl ?

    It's a performance concession. Using immutable objects has a high cost.

    You brought up Perl's COW mechanism for strings. It's specifically a mechanism to mitigate these costs. For strings. Imagine having to build COW into all your classes... And how it would work for classes that reference external (to perl) resources?

      "To avoid getting bit by this…"

      BTW, what prevents me from doing something like this?

      package Acme; use parent 'Clone'; …

      And later:

      my $object = Acme->new; my $copy = $object->clone;

      Regards, Karl

      «The Crux of the Biscuit is the Apostrophe»

        > BTW, what prevents me from doing something like this?

        Nothing.

        You can always do it explicitly, as long as a ->clone method is available.

        But the OP wants this to happen implicitly, when he does my $copy = $magic_object;

        Cheers Rolf
        (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
        Wikisyntax for the Monastery