Operator Overloading
This tutorial demonstrates Pyro's support for operator overloading by building a custom complex-number type.
You can overload most of Pyro's builtin operators — e.g. ==
, +
, -
, etc. — to customize their behaviour for your own user-defined types.
We can illustrate Pyro's support for operator-overloading by building a custom type to represent complex numbers.
Base Class
Our complex number type needs to store two values — the real part and the imaginary part:
class Complex { pub var re; pub var im; def $init(re, im) { self.re = re; self.im = im; } }
Now we can create instances of Complex
numbers, e.g.
var c = Complex(1, 2); assert c.re == 1; assert c.im == 2;
To make our Complex
numbers useful, we want to be able to compare them and to perform arithmetic with them.
Equality
By default, objects compare as equal using ==
only if they are the same object, e.g.
var c1 = Complex(1, 2); var c2 = Complex(1, 2); assert c1 == c1; assert c1 != c2;
We can overload the ==
operator for our custom Complex
number type by defining an $op_binary_equals_equals()
method:
class Complex { ... def $op_binary_equals_equals(other) { if $is_instance_of_class(other, Complex) { return self.re == other.re && self.im == other.im; } return false; } ... }
Now we can compare instances of our Complex
number type directly, e.g.
var c1 = Complex(1, 2); var c2 = Complex(1, 2); assert c1 == c2;
Overloading the ==
operator automatically overloads the !=
operator, e.g.
assert Complex(1, 2) != Complex(3, 4);
Addition
We can overload the binary addition operator, +
, for our Complex
number type by defining an $op_binary_plus()
method:
class Complex { ... def $op_binary_plus(other) { if $is_instance_of_class(other, Complex) { return Complex(self.re + other.re, self.im + other.im); } $panic("invalid operation"); } ... }
Now we can add Complex
numbers directly, e.g.
assert Complex(1, 2) + Complex(3, 4) == Complex(4, 6);
Overloading the +
operator automatically overloads the +=
operator, e.g.
var c = Complex(1, 2); c += Complex(3, 4); assert c == Complex(4, 6);
Negation
We can add support for the unary negation operator, -
, by defining an $op_unary_minus()
method:
class Complex { ... def $op_unary_minus() return Complex(-self.re, -self.im); } ... }
Now we can negate a Complex
number directly, e.g.
assert -Complex(1, 2) == Complex(-1, -2);
Multiplication
We can overload the binary multiplication operator, *
, for our Complex
number type by defining an $op_binary_star()
method:
class Complex { ... def $op_binary_star(other) { if $is_instance_of_class(other, Complex) { var re = self.re * other.re - self.im * other.im; var im = self.re * other.im + self.im * other.re; return Complex(re, im); } $panic("invalid operation"); } ... }
Now we can multiply Complex
numbers directly, e.g.
assert Complex(1, 2) * Complex(3, 4) == Complex(-5, 10);
This works for multiplying two Complex
numbers, but what if we want to multiply a Complex
number by a scalar — e.g. an i64
or an f64
?
No problem — we can make our $op_binary_star()
method a little more discerning:
class Complex { ... def $op_binary_star(other) { if $is_instance_of_class(other, Complex) { var re = self.re * other.re - self.im * other.im; var im = self.re * other.im + self.im * other.re; return Complex(re, im); } if $is_i64(other) || $is_f64(other) { return Complex(self.re * other, self.im * other); } $panic("invalid operation"); } ... }
Now we can multiply a Complex
number by a scalar, e.g.
assert Complex(1, 2) * 3 == Complex(3, 6);
We're not quite done yet. Defining an $op_binary_star()
method only overloads the binary *
operator for cases when the receiver instance is on the left-hand-side of the expression — i.e. for expressions of the form receiver * other
.
If we want to handle cases when the receiver instance is on the right-hand-side of the expression — i.e. expressions of the form other * receiver
— we need to define an $rop_binary_star()
method:
class Complex { ... def $rop_binary_star(other) { return self:$op_binary_star(other); } ... }
(Here, we simply reuse the logic we already implemented in the $op_binary_star()
method.)
Now we can multiply a Complex
number on the left by a scalar, e.g.
assert 3 * Complex(1, 2) == Complex(3, 6);
The $rop_binary_star()
method is a fallback — it's only called if the object on the left of the expression doesn't have an $op_binary_star()
method defined.
Hashing
Do we want to use our custom Complex
number type as a key in hash maps or as an entry in sets?
If so, we need to define a custom $hash()
method to ensure that instances that compare as equal using ==
also have the same hash value.
An easy way to do this for our Complex
number type is to XOR
the hashes of the real and imaginary parts, e.g.
class Complex { ... def $hash() { return $hash(self.re) ^ $hash(self.im); } ... }
Overload Methods
You can find the full set of operator-overload methods for custom types documented here.