Sign In/My Account | View Cart  
advertisement


Listen Print

Overloading

by Dave Cross
July 22, 2003

Introduction: What is Overloading?

All object-oriented programming languages have a feature called overloading, but in most of them this term means something different from what it means in Perl. Take a look at this Java example:

public Fraction(int num, int den);
public Fraction(Fraction F);
public Fraction();

In this example, we have three methods called Fraction. Java, like many languages, is very strict about the number and type of arguments that you can pass to a function. We therefore need three different methods to cover the three possibilities. In the first example, the method takes two integers (a numerator and a denominator) and it returns a Fraction object based on those numbers. In the second example, the method takes an existing Fraction object as an argument and returns a copy (or clone) of that object. The final method takes no arguments and returns a default Fraction object, maybe representing 1/1 or 0/1. When you call one of these methods, the Java Virtual Machine determines which of the three methods you wanted by looking at the number and type of the arguments.

Related Reading

Learning Perl Objects, References, and Modules
By Randal L. Schwartz

In Perl, of course, we are far more flexible about what arguments we can pass to a method. Therefore the same method can be used to handle all of the three cases from the Java example. (We'll see an example of this in a short while.) This means that in Perl we can save the term "overloading" for something far more interesting — operator overloading.

Number::Fraction — The Constructor

Imagine you have a Perl object that represents fractions (or, more accurately, rational numbers, but we'll call them fractions as we're not all math geeks). In order to handle the same situations as the Java class we mentioned above, we need to be able to run code like this:

use Number::Fraction;

my $half       = Number::Fraction->new(1, 2);
my $other_half = Number::Fraction->new($half);
my $default    = Number::Fraction->new;

To do this, we would write a constructor method like this:

sub new {
    my $class = shift;
    my $self;
    if (@_ >= 2) {
        return if $_[0] =~ /\D/ or $_[1] =~ /\D/;
        $self->{num} = $_[0];
        $self->{den} = $_[1];
    } elsif (@_ == 1) {
        if (ref $_[0]) {
            if (UNIVERSAL::isa($_[0], $class) {
                return $class->new($_[0]->{num}, $_[0]->{den});
            } else {
                croak "Can't make a $class from a ", ref $_[0];
            }
        } else {
            return unless $_[0] =~ m|^(\d+)/(\d+)|;

            $self->{num} = $1;
            $self->{den} = $2;
        }
    } elsif (!@_) {
        $self->{num} = 0;
        $self->{den} = 1;
    }

    bless $self, $class;
    $self->normalise;
    return $self;
}

As promised, there's just one method here and it does everything that the three Java methods did and more even, so it's a good example of why we don't need method overloading in Perl. Let's look at the various parts in some detail.

sub new {
    my $class = shift;
    my $self;

The method starts out just like most Perl object constructors. It grabs the class which is passed in as the first argument and then declares a variable called $self which will contain the object.

    if (@_ >= 2) {
        return if $_[0] =~ /\D/ or $_[1] =~ /\D/;
        $self->{num} = $_[0];
        $self->{den} = $_[1];

This is where we start to work out just how the method was called. We look at @_ to see how many arguments we have been given. If we've got two arguments then we assume that they are the numerator and denominator of the fraction. Notice that there's also another check to ensure that both arguments contain only digits. If this check fails, we return undef from the constructor.

     } elsif (@_ == 1) {
        if (ref $_[0]) {
            if (UNIVERSAL::isa($_[0], $class) {
                return $class->new($_[0]->num, $_[0]->den);
            } else {
                croak "Can't make a $class from a ", ref $_[0];
            }
        } else {
            return unless $_[0] =~ m|^(\d+)/(\d+)|;
            $self->{num} = $1;
            $self->{den} = $2;
        }

If we've been given just one argument, then there are a couple of things we can do. First we see if the argument is a reference, and if it is, we check that it's a reference to another Number::Fraction object (or a subclass). If it's the right kind of object then we get the numerators and denominators (using the accessor functions) and use them to call the two argument forms of new. It the argument is the wrong type of reference then we complain bitterly to the user.

If the single argument isn't a reference then we assume it's a string of the form num/den, which we can split apart to get the numerator and denominator of the fraction. Once more we check for the correct format using a regex and return undef if the check fails.

     } elsif (!@_) {
        $self->{num} = 0;
        $self->{den} = 1;
    }

If we are given no arguments, then we just create a default fraction which is 0/1.

    bless $self, $class;
    $self->normalise;
    return $self;
}

At the end of the constructor we do more of the normal OO Perl stuff. We bless the object into the correct class and return the reference to our caller. Between these two actions we pause to call the normalise method, which converts the fraction to its simplest form. For example, it will convert 12/16 to 3/4.

Number::Fraction — Doing Calculations

Having now created fraction objects, we will want to start doing calculations with them. For that we'll need methods that implement the various mathematical functions. Here's the add method:

sub add {
    my ($self, $delta) = @_;

    if (ref $delta) {
        if (UNIVERSAL::isa($delta, ref $self)) {
            $self->{num} = $self->num  * $delta->den
                + $delta->num * $self->den;
            $self->{den} = $self->den  * $delta->den;
        } else {
            croak "Can't add a ", ref $delta, " to a ", ref $self;
        }
    } else {
        if ($delta =~ m|(\d+)/(\d+)|) {
            $self->add(Number::Fraction->new($1, $2));
        } elsif ($delta !~ /\D/) {
            $self->add(Number::Fraction->new($delta, 1));
        } else {
            croak "Can't add $delta to a ", ref $self;
        }
    }
    $self->normalise;
}

Once more we try to handle a number of different types of arguments. We can add the following things to our fraction object:

  • Another object of the same class (or a subclass).
  • A string in the format num/den.
  • An integer. This is converted to a fraction with a denominator of 1.

This then allows us to write code like this:

my $half           = Number::Fraction->new(1, 2);
my $quarter        = Number::Fraction->new(1, 4);
my $three_quarters = $half;
$three_quarters->add($quarter);

In my opinion, this code looks pretty horrible. It also has a nasty, subtle bug. Can you spot it? (Hint: What will be in $half after running this code?) To tidy up this code we can turn to operator overloading.

Number::Fraction — Operator Overloading

The module overload.pm is a standard part of the Perl distribution. It allows your objects to define how they will react to a number of Perl's operators. For example, we can add code like this to Number::Fraction:

use overload '+' => 'add';

Whenever a Number::Fraction is used as one of the operands to the + operator, the add method will be called instead. Code like:

$three_quarters = $half + '3/4';

is converted to:

$three_quarters = $half->add('3/4');

This is getting closer, but it still has a serious problem. The add method works on the $half object. In general, however, that's not how an assignment should work. If you were working with ordinary scalars and had code like:

$foo = $bar + 0.75;

You would be very surprised if this altered the value of $bar. Our objects need to work in the same way. We need to change our add method so that it doesn't alter $self but instead returns the new fraction.

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

In this example, I've only shown one of the sections, but I hope it's clear how it would work. Notice that I've also renamed $self and $delta to $l and $r. I find this makes more sense as we are working with the left and right operands of the + operator.

Pages: 1, 2

Next Pagearrow