Pyro

A scripting language for people who enjoy the simpler things in life.

Version 0.9.35

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) {
        return self.re == other.re && self.im == other.im;
    }

    ...
}

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) {
        return Complex(self.re + other.re, self.im + other.im);
    }

    ...
}

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) {
        var re = self.re * other.re - self.im * other.im;
        var im = self.re * other.im + self.im * other.re;
        return Complex(re, im);
    }

    ...
}

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(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 $hash() method to ensure that instances that compare as equal using == also have the same hash value.

An easy way to do this 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.