Saigo Ueno
Saigo Ueno

Reputation: 13

Overload object operation in Perl

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

Answers (1)

Borodin
Borodin

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

Related Questions