Skip to content

Exploration: Interfaces

Matt Basta edited this page Mar 6, 2016 · 3 revisions

Unlike inheritance, interfaces allow classes with radically different type signatures to be used interchangeably.

The need for this functionality stems from the fact that interfaces are prevalent in most modern programming languages, and many common design patterns cannot be achieved without them.

Proposal

The interface keyword will be added. It can be used to create an interface in the following way:

interface MyInterface {
    # ...
}

Within the interface block, method signatures followed by semicolons can be added to define methods that must exist on implementing classes.

interface MyInterface {
    int:foo();
    float:compute(int:x, int:y);
}

Method signatures may not be declared private or final in an interface.

Interfaces may only be declared in the root of a module.

Typing

After an interface has been defined, its type exists in the current module's context. This interface type can be exported to other modules.

interface Foo {}
export Foo;

Any type may use an interface type:

interface Foo {}
object Bar {}

var variable = new Bar() as Foo;

Values must be coerced to the interface with the as keyword. This is intentional; all casting must be done explicitly. If an object is cast to an interface with an incompatible method signature or missing method, a compilation error will be raised.

Only the methods defined on an interface may be called on objects cast to the interface type:

interface Foo {}
object Bar {
    method() {}
}

var x = new Bar() as Foo;
x.method(); # Compilation error!

Objects that have been cast to an interface may not (as part of this proposal) be cast back to their original type(s).

Interfaces may be cast to other less specific interfaces, but not more specific interfaces:

interface VerySpecific {
    method();
    otherMethod();
}
interface Specific {
    method();
}
interface NotSpecific {}

object Foo {
    method() {}
    otherMethod() {}
}

var x = new Foo() as Specific;
var y = x as NotSpecific; # Fine, as NotSpecific is less specific than Specific
var z = x as VerySpecific; # Compilation error, since `otherMethod` is not available on Specific

switchtype

In a switchtype block, interfaces will only match themselves and not valid implementations.

interface Foo {}
object Bar {}

func Foo:curiosity() {
    return (new Bar()) as Foo;
}

var instance = curiosity();
switchtype instance {
    case Bar {}  # Will never be hit
    case Foo {}  # Will always be hit
}

Implementation

This section of the proposal is optional for individual BType implementations.

Rather than using the original object, the BType compiler will create a new object of a type corresponding to the interface. This object will contain a member for each method defined by the interface, and a reference to the original object. For example:

object Obj {
    method() {}
}
interface Interface {
    method();
}

var x = new Obj() as Interface;

would compile into the following pseudo-code:

struct Obj {}
void Obj_method() {}

struct Interface {
    void* origObj;
    int (*method)();
}

Obj* x_temp = new Obj();
Interface* x = new Interface(origObj=x_temp, method=Obj_method);

Future Work

  • Interface composition and inheritance (interface X {} interface Y {with X;})
  • Ability to explicitly mark an object as implementing an interface

Clone this wiki locally