Pyro

A dynamically-typed, garbage-collected scripting language.

Version 0.17.0

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.