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

I've never really done any operator overloading in perl, so I was very interesting when I saw this article on perl.com that uses Number::Fraction as an example. But as interesting as the info on overloading was, I couldn't help but get hung up on a particluar line of (non operator related) code ...

sub add { my ($l, $r, $rev) = @_; if (ref $r) { if (UNIVERSAL::isa($r, ref $l)) { return (ref $l)->new($l->{num} * $r->{den} + $r->{num} * $l->{de +n}, $r->{den} * $l->{den}); } else { ...

The key thing that jumps out at me here (unless I'm really missing something about how UNIVERSAL::isa works) is that even though this code will allow the 'add' method to keep working if $l is a Number::Fraction, and $r is a subclass of N::F, or if $l is a subclass of N::F, and $r is a subclass of the subclass, it should still work -- but what if $r and $l are instances of seperate subclasses of N::F ?

Okay, so maybe I'm babling. Consider this (contrived) hierarcy...

                 Number::Fraction
                  /           \
 Number::MixedFraction       Number::Fraction::Tk

Assuming Number::MixedFraction, and Number::Fraction::Tk are both happy to inherit the 'add' method from Number::Fraction (and get a Number::Fraction as the result) then wouldn't it make sense for Number::Fraction::add to be general enough to work in that scenerio?

Isn't this a case where hardcoding 'Number::Fraction' in the class makes sense? ...

sub add { my ($l, $r, $rev) = @_; if (ref $r) { if (UNIVERSAL::isa($r, 'Number::Fraction')) { return (ref $l)->new($l->{num} * $r->{den} + $r->{num} * $l->{de +n}, $r->{den} * $l->{den}); } else { ...

?

Replies are listed 'Best First'.
Re: if (UNIVERSAL::isa($r, ref $l))
by flounder99 (Friar) on Jul 23, 2003 at 11:38 UTC
    I noticed something very confusing in perldoc -m UNIVERSAL (in perl 5.8.0) There seems to be some contradictions. In the explanation of
    $obj->isa( TYPE ), CLASS->isa( TYPE ), isa( VAL, TYPE )
    it gives the example:
    use UNIVERSAL qw( isa ) ; $yes = isa $h, "HASH"; $yes = isa "Foo", "Bar";
    but then at the bottom of the page it says:
    These subroutines should not be imported via use UNIVERSAL qw(...). If you want simple local access to them you can do *isa = \&UNIVERSAL::isa; to import isa into your package.
    which contradicts the example. Has this been pointed out to the Doc maintainer? I've never submitted anything using perlbug and I don't know how to do an exhaustive search of the perl bug tracking mechanism. I don't want to waste anyone's time if it is a known mistake.

    --

    flounder

Re: if (UNIVERSAL::isa($r, ref $l))
by ihb (Deacon) on Jul 23, 2003 at 09:43 UTC

    If you feel a desire to hardcode the current package, use __PACKAGE__ instead. That can be right for many occasions.

    Another interesting thing though is that

    $a + $b

    can work, while

    $b + $a

    might not. This is quite unfortunate. $b might "bea" $a, but unless they're of the same class, $a won't "bea" $b.

    Neither I see why you'd require anything more from the objects than them to be a derivate of the current class if you allow them to be of different classes already. But then again, I've never claimed to be especially enlightened when it comes to OO programming. I have a bad feeling about this though.

    ihb

      Regarding the $a + $b vs $b + $a issue... Overload handles this transparently.

      Lets say that $a is a Number::Fraction and leave $b alone for moment. If we say $c = $a + $b this will be magically transformed to $c=$a->add($b,0); But when we say $c= $b + $a there is only one circumstance where it will not get transformed to $c=$a->add($b,1) and thats when $b is both blessed, and has also overriden addition itself. In the latter case it gets transformed into $b->add($a,0) assuming of course the method in the other package is called 'add' as well.

      This is quite unfortunate. $b might "bea" $a, but unless they're of the same class, $a won't "bea" $b.

      The point of this node is to show that unless $b is also overriden your comment cant be true. And if $b is overriden then it had better have its own logic to handle the situation properly.

      I do however agree with you and the OP that there is a problem here with inheriting the add method. It just wont work right in the child classes. But unless davorg had inheritance in mind I dont think its entirely wrong. Mostly maybe....


      ---
      demerphq

      <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: if (UNIVERSAL::isa($r, ref $l))
by adrianh (Chancellor) on Jul 23, 2003 at 15:48 UTC
    Assuming Number::MixedFraction, and Number::Fraction::Tk are both happy to inherit the 'add' method from Number::Fraction (and get a Number::Fraction as the result) then wouldn't it make sense for Number::Fraction::add to be general enough to work in that scenerio?

    I'd say so (although I agree with ihb and would use __PACKAGE__ rather than hardcoding the class name).

    Ideally subclasses should do at least as much as their parent class, and possibly more (or, in design by contract speak - they must meet all their parents class invarients and post-conditions, and its own or its parents pre-conditions). I'd worry about the code if this wasn't true.

Re: if (UNIVERSAL::isa($r, ref $l))
by demerphq (Chancellor) on Jul 24, 2003 at 00:19 UTC

    Id agree with the other posters that using __PACKAGE__ is the best approach. Im actually a little suprised that davorg did this. I have a feeling its either shorthand for a class not meant to be inherited, or it was modified by an unthinking editor to reduce space in the column. Hopefully he will be by soon and let us know.


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
      ...or it was modified by an unthinking editor to reduce space in the column. Hopefully he will be by soon and let us know.
      Well, It's in version 1.02 of the package.

      Hey, I edited that.

      I really don't see what advantage __PACKAGE__ has over a hardcoded class name except that it's Pythonesquely uglier.

      Alright, I can hear dozens of keyboards getting ready to type "What about inheritance, you dope?" Before you get excited, try this little program:

      package Foo; sub new { bless {}, __PACKAGE__; } package Bar; use vars qw( @ISA ); @ISA = 'Foo'; package main; my $bar = Bar->new(); print "Bar is a ", ref $bar, "\n";

      Yep, the package into which it's compiled. Nope, not all that useful. Pity.

      Update: Okay, if you don't like hardcoding the class name, there's an advantage. The Class module gets rid of the ugliness. That's kinda useful.

        You edited it did you? I see, shame on you. ;-)

        I really don't see what advantage __PACKAGE__ has over a hardcoded class name except that it's Pythonesquely uglier.

        The advantage is that using __PACKAGE__ works, whereas as written it fails the empty subclass test because of the ref $l thing

        use Number::Fraction; package Number::Fraction::Foo; use base qw/Number::Fraction/; 1; package Number::Fraction::Bar; use base qw/Number::Fraction/; 1; my $n=Number::Fraction->new(1,4); my $f=Number::Fraction::Foo->new(1,4); my $b=Number::Fraction::Bar->new(1,4); print $n+$f,$/; print $n+$b,$/; print $f+$n,$/; print $b+$n,$/; print $f+$b,$/; print $b+$f,$/; __END__ Can't add a Number::Fraction::Foo to a Number::Fraction::Foo at D:\per +l\devlib\/Number/Fraction.pm line 119 Number::Fraction::add('1/4', '1/4', '') called at D:\Temp\nf.pl li +ne 15 1/2 1/2

        If you change all the ref $l's to __PACKAGE__ (As I did to create Number::FractionP) you get the expected results:

        use Number::FractionP; package Number::FractionP::Foo; use base qw/Number::FractionP/; 1; package Number::FractionP::Bar; use base qw/Number::FractionP/; 1; my $n=Number::FractionP->new(1,4); my $f=Number::FractionP::Foo->new(1,4); my $b=Number::FractionP::Bar->new(1,4); print $n+$f,$/; print $n+$b,$/; print $f+$n,$/; print $b+$n,$/; print $f+$b,$/; print $b+$f,$/; __END__ 1/2 1/2 1/2 1/2 1/2 1/2

        So I'd say that ugliness has to take back seat to correctness. Pity.

        :-)


        ---
        demerphq

        <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
        I really don't see what advantage __PACKAGE__ has over a hardcoded class name except that it's Pythonesquely uglier.

        I prefer __PACKAGE__ (or CLASS) because I like to say the class name once and only once in the module source (you should know why that's a good motto to live by :-).

        That way I don't have to worry about grepping the module source if I change the name of the module.

      Hopefully he will be by soon and let us know.

      It's a bug in my code :)

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg

        I reckon the moral of this story for us all is that to properly test the inheritance of an overloaded module is that you need to to do a double empty subclass test and test them all against each other. (A methodology that could actually be easily automated if somebody wants to write a Test::Overload module.)


        ---
        demerphq

        <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...

        At the bare minimum I would say you could make a nice follow up article or whatnot out of this. :-)

        Nice article BTW, a very good explanation of some of the darker corners of overload.


        ---
        demerphq

        <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: if (UNIVERSAL::isa($r, ref $l))
by Hofmator (Curate) on Jul 24, 2003 at 07:06 UTC
    ... and while we are at it, I noticed a different small issue with the article.

    Towards the end, in the subs to_string and to_num it reads

    sub to_string { my $self = shift; return "$_->{num}/$_->{den}"; } sub to_num { my $self = shift; return $_{num}/$_->{den};
    while that should be (as it is in the cpan version 1.02)
    sub to_string { my $self = shift; return "$self->{num}/$self->{den}"; } sub to_num { my $self = shift; return $self->{num} / $self->{den}; }

    -- Hofmator