Reputation: 13
I want to use overloaded operators in a method which modifies an object. I also want to achieve it without duplicating the code.
To illustrate the problem, I will show a simplified version of what I am trying to do. In my original code, the add
method overloads +
and complicated_calculation
method tries to update the object.
The add
method creates a new Number
object to avoid an expression like $n + 1
modifying the object.
package Number;
use overload
'0+' => 'get_value',
'+' => 'add';
sub new {
my ($class, $value) = @_;
my $self->{value} = $value;
return bless($self, $class);
}
sub get_value {
my ($self) = @_;
return $self->{value};
}
sub set_value {
my ($self, $value) = @_;
$self->{value} = $value;
}
# Actual class has more attributes and the logic of addition includes branches.
sub add {
my ($self, $other) = @_;
print "add $other\n";
return Number->new($self->get_value + $other);
}
sub complicated_calculation {
my ($self) = @_;
# Do something complicated.
$self += 10;
}
package main;
my $n = Number->new(1);
print $n + 1 . "\n";
$n++;
print $n . "\n";
$n->complicated_calculation;
print $n . "\n";
Will output
add 1
2
add 1
2
add 10
2
I want the result of complicated_calculation
method (12) to be printed, but 2 is printed instead. The result of the complicated_calculation
method is set to an object created by the add
method, instead of to the object which called it.
I can make the complicated_calculation
method update the object using an add_in_place
method to add a number in-place, but this requires duplicated code in add
and add_in_place
which I was taught to avoid.
In the actual application the Number
class will have many more attributes, and the code for addition will be much longer.
package Number;
use overload
'0+' => 'get_value',
'+' => 'add',
'+=' => 'add_in_place',
'fallback' => 1;
sub new {
my ($class, $value) = @_;
my $self->{value} = $value;
return bless($self, $class);
}
sub get_value {
my ($self) = @_;
return $self->{value};
}
sub set_value {
my ($self, $value) = @_;
$self->{value} = $value;
}
# Actual class has more attributes and the logic of addition includes branches.
sub add {
my ($self, $other) = @_;
print "add $other\n";
return Number->new($self->get_value + $other);
}
sub add_in_place {
my ($self, $other) = @_;
print "add_in_place $other\n";
$self->set_value($self->get_value + $other);
}
sub complicated_calculation {
my ($self) = @_;
# Do something complicated.
$self += 10;
}
package main;
my $n = Number->new(1);
print $n + 1 . "\n";
$n++;
print $n . "\n";
$n->complicated_calculation;
print $n . "\n";
Will output
add 1
2
add_in_place 1
2
add_in_place 10
12
I feel that there should be a better way and would like to have some advice from you guys.
Upvotes: 1
Views: 115
Reputation: 126722
First of all, you must always use strict
and use warnings
at the top of every Perl program file you write. This applies especially when you are asking for help with your code, as it is the first line of defence against bugs and really should be your first resort before troubling others.
This is happening because the add
method is called to implement the +=
operator, which returns a new Number
object as a result. That results in the value of $self
within complicated_calculation
being changed to refer to the new Number
object that, correctly, has a value of 12. But the original value -- $n
in the main code -- still points to an object with the value of 2.
To get it to work, you could arrange that complicated_calculation
returns the new object, and the calling code assigns it to $n
. Just changing that statement to
$n = $n->complicated_calculation
will get it working.
However, it is a little strange to write stuff like that as a method. The code in the Number
class should be focused on making the object behave correctly, so all the methods should be operators. If you were writing complicated_calculation
as a subroutine in the main
package then you would be fine with
$n += 10;
print $n;
as the copying of $n
would then work correctly and transparently. It is only when you are writing a method that reassigning $self
makes no sense, because it then no longer refers to the object the calling code is using.
If you really consider complicated_calculation
to be an operator, then it should mutate the object in-place rather than relying on overload
to provide the mechanism. If you changed it to
sub complicated_calculation {
my ($self) = @_;
$self->{value} += 10;
}
then everything would work as it should.
Update
I strongly believe that you should write everything in terms of add_in_place
, which should be a private method for use only internally by the class.
Both add
and complicated_calculation
can be very simply rewritten, and there is no longer any need to write $n = $n->complicated_calculation
as the method modifies the object in-place.
This example code for the module demonstrates.
package Number;
use strict;
use warnings;
use 5.010;
use overload
'0+' => 'get_value',
'+' => 'add';
sub new {
my ($class, $value) = @_;
bless { value => $value };
}
sub get_value {
my ($self) = @_;
$self->{value};
}
sub set_value {
my ($self, $value) = @_;
$self->{value} = $value;
}
sub add {
my ($self, $other) = @_;
print "add $other\n";
Number->new($self->get_value)->add_in_place($other);
}
sub add_in_place {
my ($self, $other) = @_;
print "add_in_place $other\n";
$self->{value} += $other;
$self;
}
sub complicated_calculation {
my ($self) = @_;
$self->add_in_place(10);
}
Upvotes: 3