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:
- Declare the interface to be implemented when the type is defined. For details, see related examples in the preceding content.
- Implement the interface through extension. For details, see Extension.
- 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:
- 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.
- 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!"
}