Interfaces

An interface is used to define an abstract type. An interface contains no data but can define a behavior of a type. If a type declares that it implements an interface and implements all the members of the interface, the interface is implemented.

An interface can contain the following members:

  • Member function
  • Operator overloading function
  • Member property

These members are abstract. Implementing types must have the corresponding implementation of the members.

Defining Interfaces

A simple interface is defined as follows:

interface I { // 'open' modifier is optional.
    func f(): Unit
}

An interface is declared using the keyword interface, followed by the I identifier of the interface and the members of the interface. Interface members can be modified by the open modifier. The open modifier is optional.

After the I interface declares a member function f, to implement I for a type, you must implement a corresponding f function in the type.

Because an interface has the semantics of open by default, the open modifier is optional when an interface is defined.

As shown in the following code, class Foo is defined, and Foo is declared in the form of Foo <: I to implement the I interface.

Foo must contain the implementations of all members declared by I, that is, an f of the same type must be defined. Otherwise, a compilation error is reported because the interface is not implemented.

class Foo <: I {
    public func f(): Unit {
        println("Foo")
    }
}

main() {
    let a = Foo()
    let b: I = a
    b.f() // "Foo"
}

After a type implements an interface, the type becomes a subtype of the interface.

In the preceding example, Foo is a subtype of I. Therefore, any instance of the Foo type can be used as an instance of the I type.

In main, if you assign the variable a of the Foo type to the variable b of the I type and call the f function in b, the f version implemented by Foo is printed. The program output is as follows:

Foo

An interface can also use the sealed modifier to indicate that the interface can be inherited, implemented, or extended only within the package where the interface definition resides. The sealed modifier contains the semantics of public or open. Therefore, if the public or open modifier is provided when a sealed interface is defined, the compiler generates an alarm. Sub-interfaces that inherit a sealed interface or classes that implement a sealed interface can be modified by sealed or not be modified by sealed. If a sub-interface of a sealed interface is modified by public and is not modified by sealed, the sub-interface can be inherited, implemented, or extended outside the package. Types that inherit and implement sealed interfaces do not need to be modified by public.

package A
public interface I1 {}
sealed interface I2 {}         // OK
public sealed interface I3 {}  // Warning, redundant modifier, 'sealed' implies 'public'
sealed open interface I4 {}    // Warning, redundant modifier, 'sealed' implies 'open'

class C1 <: I1 {}
public open class C2 <: I1 {}
sealed class C3 <: I2 {}
extend Int64 <: I2 {}
package B
import A.*

class S1 <: I1 {}  // OK
class S2 <: I2 {}  // Error, I2 is sealed interface, cannot be inherited here.

Through the constraint capability of the interface, common functions can be specified for a series of types to abstract the functions.

As shown in the following code, you can define a Flyable interface and have other classes with the properties of Flyable to implement it.

interface Flyable {
    func fly(): Unit
}

class Bird <: Flyable {
    public func fly(): Unit {
        println("Bird flying")
    }
}

class Bat <: Flyable {
    public func fly(): Unit {
        println("Bat flying")
    }
}

class Airplane <: Flyable {
    public func fly(): Unit {
        println("Airplane flying")
    }
}

func fly(item: Flyable): Unit {
    item.fly()
}

main() {
    let bird = Bird()
    let bat = Bat()
    let airplane = Airplane()
    fly(bird)
    fly(bat)
    fly(airplane)
}

Compile and execute the preceding code. The following information is displayed:

Bird flying
Bat flying
Airplane flying

The members of an interface can be instance or static members. The preceding example shows the use of instance member functions. The following shows the use of static member functions.

Similar to instance member functions, static member functions require implementing types to provide implementation.

In the following example, the NamedType interface is defined. The interface contains a static member function typename to obtain the string name of each type.

Other types must implement the typename function when implementing the NamedType interface, and then the name of the type can be obtained safely on the subtype of the NamedType.

interface NamedType {
    static func typename(): String
}

class A <: NamedType {
    public static func typename(): String {
        "A"
    }
}

class B <: NamedType {
    public static func typename(): String {
        "B"
    }
}

main() {
    println("the type is ${ A.typename() }")
    println("the type is ${ B.typename() }")
}

The program output is as follows:

the type is A
the type is B

Static member functions (or properties) in an interface may or may not have a default implementation.

When it has no default implementation, it cannot be accessed through the interface type name. As shown in the following code, a compilation error is reported when the typename function of the NamedType is directly accessed. This is because the NamedType does not have the implementation of the typename function.

main() {
    NamedType.typename() // Error
}

A static member function (or property) in an interface can also have a default implementation. When another type inherits an interface that has a static function (or property) with a default implementation, the type does not need to implement the static member function (or property). The function (or property) can be accessed directly through the interface name and the type name. In the following example, the member function typename of NamedType has a default implementation, and it does not need to be re-implemented in A. In addition, it can be directly accessed through the interface name and the type name.

interface NamedType {
    static func typename(): String {
        "interface NamedType"
    }
}

class A <: NamedType {}

main() {
    println(NamedType.typename())
    println(A.typename())
    0
}

The program output is as follows:

interface NamedType
interface NamedType

Generally, we use such static members in generic functions through generic constraints.

The following uses printTypeName function as an example. When constraining the generic variant T to be a subtype of NamedType, ensure that all static member functions (or properties) in the instantiation type of T have implementations so that the implementation of the generic variant can be accessed in T.typename mode to abstract static members. For details, see Generics.

interface NamedType {
    static func typename(): String
}

interface I <: NamedType {
    static func typename(): String {
        f()
    }
    static func f(): String
}

class A <: NamedType {
    public static func typename(): String {
        "A"
    }
}

class B <: NamedType {
    public static func typename(): String {
        "B"
    }
}

func printTypeName<T>() where T <: NamedType {
    println("the type is ${ T.typename() }")
}

main() {
    printTypeName<A>() // Ok
    printTypeName<B>() // Ok
    printTypeName<I>() // Error, 'I' must implement all static function. Otherwise, an unimplemented 'f' is called, causing problems.
}

It should be noted that the members of an interface are modified by public by default. Additional access control modifiers cannot be declared. In addition, the implementing type must use public for implementation.

interface I {
    func f(): Unit
}

open class C <: I {
    protected func f() {} // Compiler Error, f needs to be public semantics
}

Inheriting Interfaces

If you want to implement multiple interfaces for a type, you can use & to separate the interfaces. The interfaces can be implemented in any sequence.

In the following example, MyInt can implement the Addable and Subtractable interfaces at the same time.

interface Addable {
    func add(other: Int64): Int64
}

interface Subtractable {
    func sub(other: Int64): Int64
}

class MyInt <: Addable & Subtractable {
    var value = 0
    public func add(other: Int64): Int64 {
        value + other
    }
    public func sub(other: Int64): Int64 {
        value - other
    }
}

An interface can inherit one or more interfaces, but cannot inherit classes. At the same time, new interface members can be added during interface inheritance.

In the following example, the Calculable interface inherits the Addable and Subtractable interfaces, and two overloaded operators multiplication and division are added.

interface Addable {
    func add(other: Int64): Int64
}

interface Subtractable {
    func sub(other: Int64): Int64
}

interface Calculable <: Addable & Subtractable {
    func mul(other: Int64): Int64
    func div(other: Int64): Int64
}

In this case, the addition, subtraction, multiplication, and division operators must be overloaded at the same time when the implementing type implements the Calculable interface. No member can be missing.

class MyInt <: Calculable {
    var value = 0
    public func add(other: Int64): Int64 {
        value + other
    }
    public func sub(other: Int64): Int64 {
        value - other
    }
    public func mul(other: Int64): Int64 {
        value * other
    }
    public func div(other: Int64): Int64 {
        value / other
    }
}

MyInt implements both Calculable and all the interfaces inherited by Calculable. Therefore, MyInt also implements Addable and Subtractable, that is, Mylnt is the subtype of Addable and Subtractable.

main() {
    let myInt = MyInt()
    let add: Addable = myInt
    let sub: Subtractable = myInt
    let calc: Calculable = myInt
}

For interface inheritance, if a sub-interface inherits a function or property that is implemented by default in the super-interface, the declaration of the function or property cannot be written in the sub-interface (that is, there is no default implementation). Instead, a new default implementation must be provided, the override modifier (or redef modifier) before the function definition is optional. If a sub-interface inherits a function or property that has no default implementation in the super-interface, only the declaration of the function or property can be written in the sub-interface (the default implementation can also be defined). In addition, the override modifier (or redef modifier) before the function declaration or definition is optional.

interface I1 {
   func f(a: Int64) {
        a
   }
   static func g(a: Int64) {
        a
   }
   func f1(a: Int64): Unit
   static func g1(a: Int64): Unit
}

interface I2 <: I1 {
    /*'override' is optional*/ func f(a: Int64) {
       a + 1
    }
    override func f(a: Int32) {} // Error, override function 'f' does not have an overridden function from its supertypes
    static /*'redef' is optional*/ func g(a: Int64) {
       a + 1
    }
    /*'override' is optional*/ func f1(a: Int64): Unit {}
    static /*'redef' is optional*/ func g1(a: Int64): Unit {}
}

Implementing Interfaces

All types can implement interfaces, including numeric types, Rune, String, struct, class, enum, Tuple, function, and other types.

A type can implement an interface in the following three ways:

  1. Declare the interface to be implemented when the type is defined. For details, see related examples in the preceding content.
  2. Implement the interface through extension. For details, see Extension.
  3. Implement the interface through language built-in interfaces. For details, see the Cangjie Programming Language Library API.

When an implementing type declares that an interface is implemented, all members required by the interface must be implemented. Therefore, the following rules must be met:

  1. The implementation of a member function or operator overloading function provided by the implementing type must be consistent with the interface in terms of the function name, parameter list and return type.
  2. Member properties must be consistent in whether being modified by mut or not, and the property type must be the same.

In most cases, as shown in the above examples, an implementing type should have the same implementations of members as that of an interface.

There is an exception. If the return value of a member function or operator overloading function in an interface is the class type, the return type of the implemented function can be its subtype.

In the following example, the return type of f in I is Base of the class type. Therefore, the return type of f implemented in C can be a Base subtype Sub.

open class Base {}
class Sub <: Base {}

interface I {
    func f(): Base
}

class C <: I {
    public func f(): Sub {
        Sub()
    }
}

In addition, members of the interface can provide default implementations for the class type. For an interface member with a default implementation, if the implementing type is a class, the class can inherit the implementation of the interface without providing its own implementation.

Note:

The default implementation is valid only if the implementing type is a class.

As shown in the following code, say in SayHi has a default implementation. Therefore, A can inherit the implementation of say when implementing SayHi, and B can provide its own implementation of say.

interface SayHi {
    func say() {
        "hi"
    }
}

class A <: SayHi {}

class B <: SayHi {
    public func say() {
        "hi, B"
    }
}

Especially, if a type implements multiple interfaces which contain the default implementation of the same member, multiple inheritance conflicts occur, and the language cannot select the most suitable implementation. In this case, the default implementation in the interface becomes invalid, and the implementing type needs to provide its own implementation.

In the following example, both SayHi and SayHello contain the implementation of say. Foo must provide its own implementation when implementing the two interfaces. Otherwise, compilation errors may occur.

interface SayHi {
    func say() {
        "hi"
    }
}

interface SayHello {
    func say() {
        "hello"
    }
}

class Foo <: SayHi & SayHello {
    public func say() {
        "Foo"
    }
}

When a struct, enum, or class implements an interface, the override modifier (or redef modifier) before the definition of a function or property is optional, regardless of whether the function or property in the interface has a default implementation.

interface I {
    func foo(): Int64 {
        return 0
    }
}
enum E <: I{
    elem
    public override func foo(): Int64 {
        return 1
    }
}
struct S <: I {
    public override func foo(): Int64 {
        return 1
    }
}

Any Type

The Any type is a built-in interface. Its definition is as follows:

interface Any {}

By default, all interfaces in Cangjie inherit the Any type, and all non-interface types implement the Any type by default. Therefore, all types can be used as subtypes of the Any type.

As shown in the following code, you can assign a series of variables of different types to the variables of the Any type.

main() {
    var any: Any = 1
    any = 2.0
    any = "hello, world!"
}