Interface Extensions

In the following example, the Array type does not implement the PrintSizeable interface. However, we can extend Array to add a member function printSize and implement PrintSizeable.

interface PrintSizeable {
    func printSize(): Unit
}

extend<T> Array<T> <: PrintSizeable {
    public func printSize() {
        println("The size is ${this.size}")
    }
}

Once an extension has been used to implement PrintSizeable for Array, within the current package it looks as if the interface was implemented at the time of the Array type definition.

Therefore, we can now use Array as an implementation type of PrintSizeable, as shown in the following code:

main() {
    let a: PrintSizeable = Array<Int64>()
    a.printSize() // 0
}

The result is as follows:

The size is 0

We can implement multiple interfaces within the same extension, separating them with &. The order of the interfaces does not matter.

As shown in the following code, we can implement I1, I2, and I3 for Foo in one extension:

interface I1 {
    func f1(): Unit
}

interface I2 {
    func f2(): Unit
}

interface I3 {
    func f3(): Unit
}

class Foo {}

extend Foo <: I1 & I2 & I3 {
    public func f1(): Unit {}
    public func f2(): Unit {}
    public func f3(): Unit {}
}

We can also declare additional generic constraints in the interface extension to satisfy specific interface requirements.

For example, we can make the Pair type implement the Eq interface, allowing Pair itself to conform to the Eq constraint, as shown in the following code:

class Pair<T1, T2> {
    var first: T1
    var second: T2
    public init(a: T1, b: T2) {
        first = a
        second = b
    }
}

interface Eq<T> {
    func equals(other: T): Bool
}

extend<T1, T2> Pair<T1, T2> <: Eq<Pair<T1, T2>> where T1 <: Eq<T1>, T2 <: Eq<T2> {
    public func equals(other: Pair<T1, T2>) {
        first.equals(other.first) && second.equals(other.second)
    }
}

class Foo <: Eq<Foo> {
    public func equals(other: Foo): Bool {
        true
    }
}

main() {
    let a = Pair(Foo(), Foo())
    let b = Pair(Foo(), Foo())
    println(a.equals(b)) // true
}

The result is as follows:

true

If the type being extended already contains some or all of the functions or properties required for it to implement the given interface, we do not need to re-implement those functions or properties in the extension. (More precisely, we cannot do that, because extensions can never hide, override or redefine the existing members.)

In the following example, we define a new interface Sizeable, implementing which enables obtaining the size of the implementing type instance via its member function size. Since we know that the type Array already contains such function, we can extend Array to implement Sizeable without actually adding a size function to the extension:

interface Sizeable {
    prop size: Int64
}

extend<T> Array<T> <: Sizeable {}

main() {
    let a: Sizeable = Array<Int64>()
    println(a.size)
}

The result is as follows:

0