Prototypes

Prototypes are similar to classes, but they are not types, they are literal instances. A prototype is somewhat equivalent to a static class. A common misconception is that prototypes are classes and that they are types. For example:

let foo = proto
    a = 1
    b = "hello!"
    c = fn(x)
        return this:a + x
    end
end

io:print(foo:a) // prints 1
io:print(foo:b) // prints "hello!"
io:print(foo:c(2)) // prints 3

This prototype contains three members: the number 1, the string “hello!”, and a function that adds the value of the member “a” to the argument.

Inheritance

Prototypes can be inherited from. For example:

let bar = proto : foo
    d = "world!"
end

io:print(bar:a) // prints 1
io:print(bar:b) // prints "hello!"
io:print(bar:c(2)) // prints 3
io:print(bar:d) // prints "world!"

Now, let’s change something in foo:

foo:a = 2
io:print(bar:a) // prints 2

This is because foo is the prototype of bar. Bar is a child of foo, so it inherits the changes made to foo. Bar is not an instance of foo, as prototypes cannot be instantiated, because they are not types.

You can change the prototype of a prototype. Let’s say we have 3 prototypes, A, B, and C.

let A = proto
    a_value = 1
end

let B = proto
    b_value = 2
end

let C = proto
    c_value = 3
end

All of these prototypes are currently independent of each other.

We can change the prototype of C to A:

prototype:setPrototypeOf(C, A)

io:print(C:a_value) // prints 1
io:print(C:b_value) // undefined, as B is not the prototype of C
io:print(C:c_value) // prints 3

And, the changes made to A are also applied to C.

A:another_value = 4
io:print(C:another_value) // prints 4

Now, we can change the prototype of C to B:

prototype:setPrototypeOf(C, B)

io:print(C:another_value) // undefined, as A is no longer the prototype of C
io:print(C:a_value) // undefined, as A is no longer the prototype of C
io:print(C:b_value) // prints 2
io:print(C:c_value) // prints 3

The rule is that if a prototype is modified, all of its children are also modified. However, if a child is modified, its parent is not modified. When the parent of a prototype is changed, all of the values in the parent are removed from the child, and all of the values in the new parent are added to the child.

Cloning

Prototypes can be cloned. The clone of a prototype is entirely detached from the original. Any changes made to the clone will not affect the original, and vice versa.

let foo = proto
    a = 1
    b = "hello!"
end

let bar = clone foo
bar:a = 2
io:print(bar:a) // prints 2

io:print(foo:a) // prints 1

Overloads

Prototypes can have overloaded functions. For example:

let foo = proto
    a = 5
    __add = fn(x) => this:a + x
end

io:print(foo + 2) // prints 7

Overloadable Members

__add, __sub, __mul, __div, __mod, __and, __or

Add, subtract, multiply, divide, modulus, logical and, and logical or.

__serialize

This function is called when the prototype is serialized. It must return a hashmap.

let foo = proto
    a = 5
    __serialize = fn
        return {
            "a" | this:a,
            "b" | this:a + 2
        }
    end
end

io:print(json:dump(foo)) // prints "{ "a": 5, "b": 7 }"

__index

This function is called when the prototype is indexed.

let foo = proto
    a = [1, 2, 3]
    __index = fn(x)
        return this:a:(x)
    end
end

io:print(foo:a) // prints "[1, 2, 3]"
io:print(foo:(2)) // prints "3"

__setIndex

This function is called when an index of the prototype is assigned.

let foo = proto
    a = [1, 2, 3]
    __setIndex = fn(x, y)
        this:a:(x) = y
    end
end

io:print(foo:a) // prints "[1, 2, 3]"
foo:(2) = 4
io:print(foo:a) // prints "[1, 2, 4]"

__iterator

This function is called when the prototype is iterated. It accepts a numeric value for index. Once the index is out of range, the function must call the iteratorExhausted function.

let foo = proto
    a = [1, 2, 3]
    __iterator = fn(x)
        if x < this:a:size()
            return this:a:(x)
        else
            iteratorExhausted()
        end
    end
end

for i in foo
    io:print(i)
end

toString

This function is called when the prototype is converted to a string. It must return a string.

let foo = proto
    a = 5
    toString = fn
        return "a value is: " + this:a
    end
end

io:print(foo) // prints "a value is: 5"