Overloading
by Dave Cross
|
Pages: 1, 2
Overloading Non-Commutative Operators
We can now happily handle code like:
$three_quarters = $half + '1/4';
Our object will do the right thing — $three_quarters will
end up as a Number::Fraction object that contains the value
3/4. What will happen if we write code like this?
$three_quarters = '1/4' + $half;
The overload modules handle this case as well. If your object
is either operand of one of the overloaded operators, then your method
will be called. You get passed an extra argument which indicates whether your
object was the left or right operand of the operator. This argument is
false if your object is the left operand and true if it is the right
operand.
For commutative operators you probably don't need to take any notice of this argument as, for example:
$half + '1/4'
is the same as:
'1/4' + $half
However, for non-commutative operators (like - and
/) you will need to do something like this:
sub subtract {
my ($l, $r, $swap) = @_;
($l, $r) = ($r, $l) if $swap;
...
}
Overloadable Operators
Just about any Perl operator can be overloaded in this way. This is a partial list:
- Arithmetic:
+,+=,-,-=,*,*=,/,/=,%,%=,**,**=,<<,<<=,>>,>>=,x,x=,.,.= - Comparison:
<,<=,>,=>,==,!=,<=>lt,le,gt,ge,eq,ne,cmp - Increment/Decrement:
++,--(both pre- and post- versions)
A full list is given in overload.
It's a very long list, but thankfully you rarely have to supply an implementation for more than a few operators. Perl is quite happy to synthesize (or autogenerate) many of the missing operators. For example:
- ++ can be derived from +
- += can be derived from +
- - (unary) can be derived from - (binary)
- All numeric comparisons can be derived from
<=> - All string comparisons can be derived from
cmp
Two other special operators give finer control over this autogeneration of
methods. nomethod defines a subroutine that is called when no
other function is found and fallback controls how hard Perl tries
to autogenerate a method. fallback can have one of three
values:
undef- Attempt to autogenerate methods and
dieif a method can't be autogenerated. This is the default. 0- Never try to autogenerate methods.
1- Attempt to autogenerate methods but fall back on Perl's default behavior for the the object if a method can't be autogenerated.
Here's an example of an object that will die gracefully when an unknown
operator is called. Notice that the nomethod subroutine is passed
the usual three arguments (left operand, right operand, and the swap flag)
together with an extra argument containing the operator that was used.
use overload
'-' => 'subtract',
fallback => 0,
nomethod => sub {
croak "illegal operator $_[3]"
};
Three special operators are provided to control type conversion. They
define methods to be called if the object is used in string, numeric, and
boolean contexts. These operators are denoted by q{""},
0+, and bool. Here's how we can use these in
Number::Fraction:
use overload
q{""} => 'to_string',
'0+' => 'to_num';
sub to_string {
my $self = shift;
return "$_->{num}/$_->{den}";
}
sub to_num {
my $self = shift;
return $_{num}/$_->{den};
}
Now, when we print a Number::Fraction object, it will be
displayed in num/den format. When we use the object in a numeric
context, Perl will automatically convert it to its numeric equivalent.
We can use these type-conversion and fallback operators to cut down the number of operators we need to define even further.
use overload
'0+' => 'to_num',
fallback => 1;
Now, whenever our object is used where Perl is expecting a number and we
haven't already defined an overloading method, Perl will try to use our object
as a number, which will, in turn, trigger our to_num method. This
means that we only need to define operators where their behavior will differ
from that of a normal number. In the case of Number::Fraction, we
don't need to define any numeric comparison operators since the numeric value of
the object will give the correct behavior. The same is true of the string
comparison operators if we define to_string.
Overloading Constants
We've come a long way with our overloaded objects. Instead of nasty code like:
use Number::Fraction;
$f = Number::Fraction->new(1, 2);
$f->add('1/4');
we can now write code like:
use Number::Fraction;
$f = Number::Fraction->new(1, 2) + '1/4';
There are still, however, two places where we need to use the full name of
the class — when we load the module and when we create a new fraction
object. We can't do much about the first of these, but we can remove
the need for that ugly new call by overloading
constants.
You can use overload::constant to control how Perl interprets
constants in your program. overload::constant expects a hash
where the keys identify various kinds of constants and the values are
subroutines which handle the constants. The keys can be any of
integer (for integers), float (for floating point
numbers), binary (for binary, octal, and hex numbers),
q (for strings), and qr (for the constant parts of
regular expressions).
When a constant of the right type is found, Perl will call the associated
subroutine, passing it the string representation of the constant and the way
that Perl would interpret the constant by default. Subroutines associated with
q or qr also get a third argument -- either
qq, q, s, or tr --which
indicates how the string is being used in the program.
As an example, here is how we would set up constant handlers so that strings
of the form num/den are always converted to the equivalent
Number::Fraction object:
my %_const_handlers =
(q => sub {
return __PACKAGE__->new($_[0]) || $_[1]
});
sub import {
overload::constant %_const_handlers if $_[1] eq ':constants';
}
sub unimport {
overload::remove_constant(q => undef);
}
We've defined a hash, %_const_handlers, which only contains one
entry as we are only interested in strings. The associated subroutine calls the
new method in the current package (which will be
Number::Fraction or a subclass) passing it the string as found in
the program source. If this string can be used to create a valid
Number::Fraction object, a reference to that object is returned.
If a valid object isn't returned then the subroutine returns its second
argument, which is Perl's default intepretation of the constant. As a result,
any strings in the program that can be intepreted as a fraction are converted
to the correct Number::Fraction object and other strings are left
unchanged.
The constant handler is loaded as part of our package's import
subroutine. Notice that it is only loaded if the import subroutine
is passed the optional argument :constants. This is because this
is a potentially big change to the way that a program's source code is
interpreted so we only want to turn it on if the user wants it.
Number::Fraction can be used in this way by putting the following
line in your program:
use Number::Fraction ':constants';
If you don't want the scary constant-refining stuff you can just use:
use Number::Fraction;
Also note that we've defined an unimport subroutine which
removes the constant handler. An unimport subroutine is called
when a program calls no Number::Fraction — it's the opposite
of use. If you're going to make major changes to the way that Perl
parses a program then it's only polite to undo your changes if the programmer
askes you to.
Conclusion
We've finally managed to get rid of most of the ugly class names from our code. We can now write code like this:
use Number::Fraction ':constants';
my $half = '1/2';
my $three_quarters = $half + '1/4';
print $three_quarters; # prints 3/4
I hope you can agree that this has the potential to make code far easier to read and understand.
Number::Fraction is available on the CPAN. Please feel free to
take a closer look at how it is implemented. If you come up with any more
interesting overloaded modules, I'd love to hear about them.

