Users › 06 Interfaces and polymorphism

Interfaces and polymorphism

Interfaces are how Lopolith does abstraction. There is no Object root type, so every polymorphic value has a precise static type.

1. Declaring an interface

An interface is its own file, like any other type:

%interface com.example.todo.TaskSink
    void add(String title)
    int size()

Interfaces may also declare fields and methods with default bodies:

%interface com.example.Shape
    int id
    int area()
    int areaTwice() {
        return this.area() * 2
    }

A default body may read interface fields and call interface methods; those calls dispatch to the implementing class at runtime.

2. Implementing an interface

Every interface member must be bound explicitly with Interface@member. The member name may differ from the interface's — only the signature has to match.

%class com.example.todo.TaskList implements TaskSink
    #import com.example.todo.TaskSink
    int _count

    void add(String title) TaskSink@add {
        ...
    }

    int size() TaskSink@size {
        return this._count
    }

3. Class tokens &I

&Shape is the class that implements an interface, not an instance. Tokens let you pass a class around and instantiate it later:

void start(&Activity cls) {
    Activity a = new cls()      // instantiate from the token
}

start(&MainActivity)            // pass a class token

4. No root type, no downcasts

Because there is no Object, a reference is only assignable to its own class, an interface it implements, or null. Casting an interface back to a class is rejected at compile time — the runtime performs no type checks, so the language simply does not allow it.

5. Interfaces in practice: exceptions

The built-in Exception interface is an ordinary interface with default methods. A custom exception is just a class that implements it and binds the three members:

%simple com.example.ifaces.Demo implements Main
    #import Std
    int run(int argc, String[] argv) Main@run {
        try {
            throw new $MyError("boom", 42)
        } catch (Exception e) {
            Std.print("caught: ", e.toString(), " code=", e.toInt(), "\n")
        }
        return 0
    }

%sub MyError implements Exception
    String msg
    int code

    void ___init(String msg, int code) {
        this.msg = msg
        this.code = code
    }

    String getName() Exception@getName {
        return "MyError"
    }

    String toString() Exception@toString {
        return "MyError: " + this.msg
    }

    int toInt() Exception@toInt {
        return this.code
    }
caught: MyError: boom code=42

The task-list example uses a real interface (TaskSink) with two implementations in the same class.