Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Overloading '='

by dchetlin (Friar)
on Sep 23, 2000 at 16:38 UTC ( [id://33772]=perltutorial: print w/replies, xml ) Need Help??

Table of Contents

  • What is an object, really?
  • What happens when you assign a non-overloaded object?
  • Altering objects
  • Overloading addition
  • Perl's Promise
  • What the heck is a mutator?
  • Overloading a mutator
  • Overloading the copy constructor (`=')
  • Wrapping things up

Intro

The most confusing aspect of using operator overloading in Perl is undoubtedly the operator `='. It is natural to assume, at first glance, that overloading `=' means that you're overloading assignment for the class in question. When that doesn't happen, going to the documentation isn't much better, as overload.pm is quite cryptic on this matter.

The difficult thing to understand about `=' is what it really affects. Invariably, users think that overloading `=' causes your specified code to be called when an overloaded object is assigned to another via `my $newobj = $obj' or a similar construct. It is necessary to think about the process from a Perl point of view to understand why this is not the case.

What is an object, really?

In Perl, an object is simply a variable, be it scalar, array, hash, or something more exotic, that has been `bless'ed (note to self: try not to use the word "thingy" in this tutorial). We refer to this variable by holding a reference to it. If we do this:

package MyPackage; sub new { my $self = $_[0]; bless { foo => $_[1] }, ref($self) || $self; } package main; my $a = MyPackage::->new(1);

We've created an object of type MyPackage. However, $a itself is not the object, $a is not a member of MyPackage, and it hasn't been blessed. It simply points to the object we created.

    /---\         /--------------------------------\
    | a | ------> | instance of MyPackage: foo = 1 |
    \---/         \--------------------------------/

What happens when you assign a non-overloaded object?

And now, we create a new variable $b, and assign $a to it:

package main; my $b = $a;

What has happened here? Remember that $a and $b are references, and they're just pointing to other things. If $a was a reference to an unblessed array, and we executed the above assignment, would we create a brand new array for $b to point to? Of course not. $b would be pointing to the same array. So in our case, all that has changed is:

    /---\         /--------------------------------\
    | a | ------> | instance of MyPackage: foo = 1 |
    \---/    /--> \--------------------------------/
             |
    /---\    |
    | b | ---/
    \---/

We now have two variables pointing to the same object. Things are as they should be.

Altering objects

Ah, but what happens if we try to change one of them? Well, it depends on how you try to change it. If you treat it like a normal reference to an object, overloading has nothing to do with it. If you go and modify `$a->{foo}', it will of course be reflected when you ask for `$b->{foo}'. Just like always, it's your responsibility to create a new copy if you want one. So the only question is what happens if you try to modify them with an overloaded operation?

my $c = $a + $b;

Well, actually, nothing, because we forgot to overload addition, so Perl has no way of knowing how to add a MyPackage.

Overloading addition

package MyPackage; use overload q/+/ => \&add; sub add { $_[0]->new( $_[0]->{foo} + (ref($_[1]) ? $_[1]->{foo} : $_[1]) ) }

In the add subroutine, we're creating a new instance of MyPackage with its foo attribute initialized to the foo attribute of the first operand plus either the foo attribute of the second operand if it's a MyPackage and the actual value of the second operand if it's not. (In real code we'd probably do a little more error checking to make sure we're not passed, say, an IO::Socket as our second operand).

package main; my $c = $a + $b;

This time, it works. If you take a look at $c, it points to a brand new instance of MyPackage.

    /---\         /--------------------------------\
    | a | ------> | instance of MyPackage: foo = 1 |
    \---/    /--> \--------------------------------/
             |
    /---\    |
    | b | ---/
    \---/
    /---\         /--------------------------------\
    | c | ------> | instance of MyPackage: foo = 2 |
    \---/         \--------------------------------/

So in the case of a binary operation, Perl relies on us to provide the right subroutine. If we'd had our add subroutine return an reference to a URI object, it would have still worked, and $c would now point to that URI object. This would be bizarre, of course, but unfortunately no one has come up with a module such that we could write `no bizarre qw/constructs/', and so Perl would merrily give us back the URI object.

Perl's Promise

So what happens when we use something like `++'? To understand this, we have to understand what happens inside the works when we assign one overloaded object to another, like we did above with `$b = $a'.

When we do a pure assignment, like we discussed above, Perl treats the operands ($b and $a) as normal references and has them point to the same thing. That's all. Well ... almost all. Perl also makes us a "promise".

Dear dlc,
If at any point in this program you use a mutator on either $a or $b, I
promise to create a new object for $b to point to so the mutator doesn't
affect both variables. If you change either $a or $b by normal means
before that, this promise is null and void.

Your pal,
-Perl

Why does it do this? Well, Perl's whole philosophy revolves around DWIM, which stands for Do What I Mean. When I assign $a to $b, it doesn't yet know "what I mean" -- am I going to treat $a and $b like usual references, or like references to overloaded objects? It can't tell, so it hedges its bets and waits to see what I'll do with them. If I don't use a mutator, nothing changes. If I do use a mutator, it realizes that I'm using the overloaded properties, and clones me a new object, because if it didn't, and `$a++' also modified what $b was pointing to, that would almost certainly not be "what I mean".

What the heck is a mutator?

A mutator is an operator that modifies one of its operands. Mutators that we can overload include `++', `--', and all of the assignment operators, such as `+=', `&=', etc. Note the subtle difference between an operator that modifies an operand, which is a mutator, and one that sets an operand, which is the assignment operator `='.

So Perl is basically telling us that we can safely use mutators on references to objects, even when two references are pointing to the same object because we've used `=' to assign them.

This may still sound confusing, so let's keep plugging away at our example:

$b++;

Scroll up if you don't remember what our data structures looked like before -- $a and $b pointed to the same object, because we assigned $b to $a using `='. Perl remembers this, and so when we try to use the mutator `++' here, it says "Aha! I promised dlc that I'd make this work!"

And it does work, even though we haven't overloaded `++' or `='. Perl translates `$b++' into `$b = $b + 1', because it's smart enough to know how to construct non-overloaded operators from operators we have overloaded. And because we did overload addition, we get a reference to a new MyPackage object back from the call to add(), and it's assigned to $b:

    /---\         /--------------------------------\
    | a | ------> | instance of MyPackage: foo = 1 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | b | ------> | instance of MyPackage: foo = 2 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | c | ------> | instance of MyPackage: foo = 2 |
    \---/         \--------------------------------/

So why would we ever need to overload `=', then, if Perl is smart enough to do all of this for us? Well, when more complicated things happen, Perl won't necessarily be able to figure out a copy constructor (copy constructor is a fancy phrase for "the thing that gets called when Perl has to make good on its promise") and we'll have to write it ourselves.

Overloading a mutator

Let's say we decide that autoincrement for MyPackage has to do something special -- it's going to double the value in foo, rather than add 1 to it.

package MyPackage; use overload q/++/ => sub { $_[0]->{foo}*=2; shift }; package main; $d = $c; $d++;

Oops -- we get a fatal runtime error:

  Operation `=': no method found, argument in overloaded package
  MyPackage at overload.pl line 26.

So now it's asking for the copy constructor, because we have a custom autoincrement.

Overloading the copy constructor (`=')

We'll add a very simple copy constructor:

package MyPackage; use overload q/=/ => sub { $_[0]->new($_[0]->{foo}) };

All we're doing here is asking for a new object, which is pretty much exactly what was happening before we added our custom autoincrement. So, we try the above code again:

package main; $d = $c;

Let's stop here and look at our structures after executing that last line of code:

    /---\         /--------------------------------\
    | a | ------> | instance of MyPackage: foo = 1 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | b | ------> | instance of MyPackage: foo = 2 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | c | ------> | instance of MyPackage: foo = 2 |
    \---/    /--> \--------------------------------/
             |
    /---\    |
    | d | ---/
    \---/

And now, we do the increment:

$d++;

And voila, our new autoincrement has worked:

    /---\         /--------------------------------\
    | a | ------> | instance of MyPackage: foo = 1 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | b | ------> | instance of MyPackage: foo = 2 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | c | ------> | instance of MyPackage: foo = 2 |
    \---/         \--------------------------------/
    /---\         /--------------------------------\
    | d | ------> | instance of MyPackage: foo = 4 |
    \---/         \--------------------------------/

Wrapping things up

What does all of this mean?

  • You rarely need to worry about overloading the copy constructor unless you also overload `++' (or another mutator), which in turn you rarely need to worry about unless you want it to mean something other than += 1 (or the standard meaning for the mutator you've chosen).
  • When you do overload `++' (or some other mutator), you'll need to overload `=' as well, but in general it will be a very straightforward function, doing pretty much exactly what Perl does underneath when you don't overload your mutators but let it derive them for you.
  • The documentation for overload is confusing and oddly ordered. I hope to work on that soon.

-dlc

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perltutorial [id://33772]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (5)
As of 2024-03-19 02:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found