Links

Categories

Tags


« | Main | »

JewelScript 1.3: Inheritance, virtual methods, mix-in classes, closures

By Jewe | January 2, 2015

A brief overview for the upcoming release of the library.

The next release will add some major new features. If you have been following this blog or it’s RSS feed lately, you may have noticed that I have been quite busy over the holidays. There were some things on my wish list for the language that I always wanted to add — and finally I found the time and motivation to do it.

Fully featured single inheritance

I always wanted JewelScript to support true inheritance. However, I didn’t know how to implement it properly. Luckily, a few days back I had an idea. I tried it out and voilá — it works and it wasn’t even half as painful to implement as I expected.

As a result, you can extend your script classes easily now. It works the same way as you may know from Java or C#:

class A
{
    method A(string s)
    {
    }
    method M()
    {
    }
    method N()
    {
    }
}

class B extends A
{
    method B(int i, string s) extends A(s)
    {
    }
    method N() // override
    {
        base.N(); // and call base
    }
}

virtual interfaces, classes, methods

Since we can now inherit functions from another class, it is important to define how methods derived from our base are called. Similar to the native world, virtual calls to methods are more costly than static calls. Therefore the compiler will create static calls by default.

A static call will always call the specified code. To stick with the code example above, if A::N() calls A::M(), then that will always be the case, even if class B overrides M():

class A
{
    method A(string s)
    {
    }
    method M()
    {
    }
    method N()
    {
        M(); // static call to M()
    }
}

class B extends A
{
    method B(int i, string s) extends A(s)
    {
    }
    method M() // override M()
    {
        // this is not called if we call B::N()!
    }
}

If we create an instance of B and call N(), this will call A::N(), which in turn will end up in A::M() even though we have overridden that method. To change this, we must make A::M() virtual:

class A
{
    method A(string s)
    {
    }
    virtual method M()
    {
    }
    method N()
    {
        M(); // virtual call to M()
    }
}

class B extends A
{
    method B(int i, string s) extends A(s)
    {
    }
    method M() // override M()
    {
        // we end up here if we call B::N()
    }
}

As you can see, you only have to specify virtual when you declare a method for the first time. The attribute will be inherited by all descendants of the class.

Instead of making single methods virtual, you have the option to make the entire class virtual. However, since virtual calls are more costly than static ones, I recommend doing this only if you are certain that your entire class needs to be virtual.

virtual class A
{
    method A(string s)
    {
    }
    method M()
    {
    }
    method N()
    {
        M(); // virtual call to M()
    }
}

If your class implements an interface, JewelScript will implicitly make all methods derived from the interface virtual:

interface I
{
    method M();
}

class A implements I
{
    method A(string s)
    {
    }
    method M()
    {
    }
    method N()
    {
        M(); // virtual call to M()
    }
}

Multiple inheritance, ‘mix-in’ classes

Unlike extends the keyword inherits allows us to inherit multiple base classes. Technically this is an entirely different mechanism, however.

While ‘extends’ makes the new class compatible to all super classes, ‘inherits’ will not make the new class compatible to any of it’s bases.

Instead, all members of the base class are relocated into the new class, they are literally mixed in. The new class is independent from it’s bases and doesn’t share code or data with them.

class A
{
    method A(string s)
    {
    }
}

class B
{
    method B(int i)
    {
    }
}

class C inherits A, B
{
    method C() inherits A("hello"), B(12)
    {
    }
}

Class C in this example inherits all variables and code from it’s two base classes. However, you will not be able to convert references of C to either B or A.

While you may override an inherited mix-in method, you cannot call base class implementation. Instead, your override will simply replace the inherited mix-in method.

JewelScript allows combining interface implementation or single inheritance with mix-in classes, in which case mix-in methods may implement the interface for your class:

interface I
{
    method M();
    method N();
}
class A implements I
{
    method A() {}
}
class B
{
    method B() {}
    method M() { println("Hello!"); }
}
class C
{
    method C() {}
    method N() { println("Goodbye!"); }
}
class D // Extend A and have B and C implement / override M() and N() for us!
    extends A
    inherits B, C
{
    method D(string s)
        extends A()
        inherits B(), C()
    {
    }
}

For a rather elaborate example of using mix-in classes, see this post.

Closures

A closure is an anonymous local function or method that has access to the parent function’s local variables. Up to now, JewelScript delegates were not closures. This basically meant that you had to pass all data your delegate needed as parameters to the local function:

function Foo()
{
    string[] names = { "Judy", "Christopher", "Helen", "James", "Sandra", "Rick" };

    string hello = "Hello ";
    names.enumerate(function(e, a)
    {
        string hello = a;
        println(hello + e);

    }, hello);
}

In this little example, we want to print all elements from an array of names. We also want to dynamically prefix each name with a greeting, “Hello” in this case. Using JewelScript’s old local delegates, we had to pass this string as an additional argument to the local function.

What makes this even more inconvenient is that the additional argument for the delegate is a type-less variable. Since the array class doesn’t know what type of data users may wish to pass on to the delegate, it makes additional arguments type-less, so you can pass anything to the delegate. This forces us to cast argument ‘a’ back to a string in the local function.

This all has become much easier now that anonymous local functions are closures. The local function can just access the string variable ‘hello’ directly. In fact, it may access any of the parent function’s local variables.

function Foo()
{
    string[] names = { "Judy", "Christopher", "Helen", "James", "Sandra", "Rick" };

    string hello = "Hello ";
    names.enumerate(function(e, a) { println(hello + e); }, null);
}

Keep in mind that delegates are first class functions — they can be used like any other value in the language. They can be stored in variables, passed into functions and returned by functions. So we could end up trying to return a function that accesses it’s parent function’s local variables!

JewelScript actually allows you to do this, even though the parent’s variables will be gone once the parent function returns. To make this possible, the closure will capture a snapshot of the parent function’s stack when it’s created:

delegate string Enumerator(int i);

function Enumerator MakeClosure(string greeting)
{
    string[] names = { "Judy", "Christopher", "Helen", "James", "Sandra", "Rick" };
    return function {
        return greeting + names[i];
    };
}

function main()
{
    Enumerator enu = MakeClosure("Hi ");

    println( enu(0) ); // print Hi Judy
    println( enu(5) ); // print Hi Rick
}

Since a closure maintains it’s own private stack context, it is similar to JewelScript’s co-function. Although unlike a co-function, a closure is not a thread, it can be used to implement generators and finite state machines:

delegate int Generator();

function Generator MakeGenerator(int start)
{
    return function { return start++; };
}

function main()
{
    Generator gen = MakeGenerator(105);

    println( gen() ); // print 105
    println( gen() ); // print 106
}

Closures are obviously very powerful and convenient. However, they come with a price. Due to their complexity, calling a closure is more costly than calling a normal delegate. You should keep that in mind when using them.

Topics: docs, news | Comments Off on JewelScript 1.3: Inheritance, virtual methods, mix-in classes, closures

Comments are closed.