Overloading
by Dave CrossJuly 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 |
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 |

