扩展概述
扩展可以为在当前 package 中可见的类型(除函数、元组、接口)添加新功能。
当不能破坏被扩展类型的封装性,但希望添加额外的功能时,可以使用扩展。
可以添加的功能包括:
添加成员函数
添加操作符重载函数
添加成员属性
实现接口
扩展虽然可以添加额外的功能,但不能变更被扩展类型的封装性,因此扩展不支持以下功能:
扩展不能增加成员变量。
扩展的函数和属性必须拥有实现。
扩展的函数和属性不能使用 open、override、 redef修饰。
扩展不能访问被扩展类型中 private 修饰的成员。
根据扩展有没有实现新的接口,扩展可以分为 直接扩展 和 接口扩展 两种用法。直接扩展即不包含额外接口的扩展;接口扩展即包含接口的扩展,接口扩展可以用来为现有的类型添加新功能并实现接口,增强抽象灵活性。
直接扩展
一个简单的扩展语法结构示例如下:
1 2 3 4 5 extend String { public func printSize() { println("the size is ${this.size}") } }
如上例所示,扩展使用 extend 关键字声明,其后跟着被扩展的类型 String 和扩展的功能。
当为 String 扩展了 printSize 函数之后,就能在当前 package 内对 String 的实例访问该函数,就像是 String 本身具备该函数。
1 2 3 4 main() { let a = "123" a.printSize() // the size is 3 }
被扩展类型是泛型类型时,有两种扩展语法可以对泛型类型扩展功能。
一种是针对特定泛型实例化类型进行扩展 ,关键字 extend 后允许带一个任意实例化完全的泛型类型。为这些类型增加的功能只有在类型完全匹配时才能使用,且泛型类型的类型实参必须符合泛型类型定义处的约束要求。
例如下面所示的 Foo<T>。
1 2 3 4 5 6 class Foo<T> where T <: ToString {} extend Foo<Int64> {} // Ok class Bar {} extend Foo<Bar> {} // Error
另一种是在 extend 后面引入泛型形参的泛型扩展 。泛型扩展可以用来扩展未实例化或未完全实例化的泛型类型。在 extend 后声明的泛型形参必须被直接或间接使用在被扩展的泛型类型上。为这些类型增加的功能只有在类型和约束完全匹配时才能使用。
例如下面所示的 MyList<T>。
1 2 3 4 5 6 7 8 9 10 class MyList<T> { public let data: Array<T> = Array<T>() } extend<T> MyList<T> {} // OK extend<R> MyList<R> {} // OK extend<T, R> MyList<(T, R)> {} // OK extend MyList {} // Error extend<T, R> MyList<T> {} // Error extend<T, R> MyList<T, R> {} // Error
对于泛型类型的扩展,可以在其中声明额外的泛型约束,来实现一些有限情况下才能使用的函数。
例如可以定义一个叫 Pair 的类型,这个类型可以方便地存储两个元素(类似于 Tuple)。
希望 Pair 类型可以容纳任何类型,因此两个泛型变元不应该有任何约束,这样才能保证 Pair 能容纳所有类型。
但同时又希望当两个元素可以判等的时候,让 Pair 也可以判等,这时就可以用扩展来实现这个功能。
如下面的代码所示,使用扩展语法,约束了 T1 和 T2 在支持 equals 的情况下,Pair 也可以实现 equals 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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> 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 }
接口扩展
例如下面的例子,类型 Array 本身没有实现接口 PrintSizeable,但可以通过扩展的方式为 Array 增加额外的成员函数 printSize,并实现 PrintSizeable。
1 2 3 4 5 6 7 8 9 interface PrintSizeable { func printSize(): Unit } extend<T> Array<T> <: PrintSizeable { public func printSize() { println("The size is ${this.size}") } }
当使用扩展为 Array 实现 PrintSizeable 之后,就相当于在 Array 定义时实现接口 PrintSizeable。
因此可以将 Array 作为 PrintSizeable 的实现类型来使用,代码如下所示。
1 2 3 4 main() { let a: PrintSizeable = Array<Int64>() a.printSize() // 0 }
编译执行上述代码,输出结果为:
可以在同一个扩展内同时实现多个接口,多个接口之间使用 & 分开,接口的顺序没有先后关系。
如下面代码所示,可以在扩展中为 Foo 同时实现 I1、I2、I3。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 {} }
也可以在接口扩展中声明额外的泛型约束,来实现一些特定约束下才能满足的接口。
例如可以让上面的 Pair 类型实现 Eq 接口,这样 Pair 自己也能成为一个符合 Eq 约束的类型,如下代码所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 }
编译执行上述代码,输出结果为:
如果被扩展的类型已经包含接口要求的函数或属性,那么在扩展中不需要并且也不能重新实现这些函数或属性。
例如下面的例子,定义了一个新接口 Sizeable,目的是获取某个类型的 size,而已经知道 Array 中包含了这个函数,因此就可以通过扩展让 Array 实现 Sizeable,而不需要添加额外的函数。
1 2 3 4 5 6 7 8 9 10 interface Sizeable { prop size: Int64 } extend<T> Array<T> <: Sizeable {} main() { let a: Sizeable = Array<Int64>() println(a.size) }
编译执行上述代码,输出结果为:
当多个接口扩展实现的接口存在继承关系时,扩展将按照“先检查实现父接口的扩展,再检查子接口的扩展”的顺序进行检查。
例如,接口 I1 存在一个子接口 I2,且 I1 中包含一个默认实现,类型 A 的两个扩展分别实现了父子接口,根据以上检查顺序,实现 I1 的扩展将会优先检查,然后再检查实现 I2 的扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface I1 { func foo(): Unit { println("I1 foo") } } interface I2 <: I1 { func foo(): Unit { println("I2 foo") } } class A {} extend A <: I1 {} // first check extend A <: I2 {} // second check main() { A().foo() }
编译执行上述代码,输出结果为:
以上例子中,当检查实现 I1 的扩展时,会从 I1 中继承 foo 函数。在检查实现 I2 的扩展时,由于 A 中已存在一个继承的,且签名相同的默认实现 foo ,此时 foo 将被覆盖。因此,调用 A 的 foo 函数时,最终指向 I2(子接口)中的实现。
如果同一类型的两个接口扩展实现的接口存在继承冲突,导致无法确定检查顺序时,将会报错。
1 2 3 4 5 6 7 8 interface I1 {} interface I2 <: I1 {} interface I3 {} interface I4 <: I3 {} class A {} extend A <: I1 & I4 {} // error: unable to decide which extension happens first extend A <: I2 & I3 {} // error: unable to decide which extension happens first
如果同一类型的两个接口扩展实现的接口不存在继承关系,将会被同时检查。
1 2 3 4 5 6 7 8 9 10 interface I1 { func foo() {} } interface I2 { func foo() {} } class A {} extend A <: I1 {} // Error, multiple default implementations, need to re-implement 'foo' in 'A' extend A <: I2 {} // Error, multiple default implementations, need to re-implement 'foo' in 'A'
注意:
当类 A 有个泛型基类 B<T1,...,Tn>,B<T1,...,Tn> 扩展了一个接口 I<R1,...,Rn>,I<R1,...,Rn> 带有默认实现的实例或者静态函数(比如 foo),该函数没有在 B<T1,...,Tn> 及其扩展中被重写,且类 A 没有直接实现接口 I<R1,...,Rn> 时,通过类 A 的实例调用函数 foo 时会产生非预期行为。计划在后续版本修复该问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 interface I<N> { func foo(n: N): N {n} } open class B<T> {} extend<T> B<T> <: I<T> {} class A <: B<Int64>{} main() { A().foo(0) // this call triggers unexpected behaviour }
访问规则
扩展的修饰符
扩展本身不能使用修饰符修饰。
例如,下面的例子中对 A 的直接扩展前使用了 public 修饰,将编译报错。
1 2 3 public class A {} public extend A {} // Error, expected no modifier before extend
扩展成员可使用的修饰符有:static、public、protected、internal、private、mut。
使用 private 修饰的成员只能在本扩展内使用,外部不可见。
使用 internal 修饰的成员可以在当前包及子包(包括子包的子包)内使用,这是默认行为。
使用 protected 修饰的成员在本模块内可以被访问(受导出规则限制)。当被扩展类型是 class 时,该 class 的子类定义体也能访问。
使用 static 修饰的成员,只能通过类型名访问,不能通过实例对象访问。
对 struct 类型的扩展可以定义 mut 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package p1 public open class A {} extend A { public func f1() {} protected func f2() {} private func f3() {} static func f4() {} } main() { A.f4() var a = A() a.f1() a.f2() }
扩展内的成员定义不支持使用 open、override、redef 修饰。
1 2 3 4 5 6 7 8 9 10 class Foo { public open func f() {} static func h() {} } extend Foo { public override func f() {} // Error public open func g() {} // Error redef static func h() {} // Error }
扩展的孤儿规则
为一个其他 package 的类型实现另一个 package 的接口,可能造成理解上的困扰。
为了防止一个类型被意外实现不合适的接口,仓颉不允许定义孤儿扩展,即既不与接口(包含接口继承链上的所有接口)定义在同一个包中,也不与被扩展类型定义在同一个包中的接口扩展 。
如下代码所示,不能在 package c 中,为 package a 里的 Foo 实现 package b 里的 Bar。
只能在 package a 或者在 package b 中为 Foo 实现 Bar。
1 2 3 4 5 6 7 8 9 10 11 // package a public class Foo {} // package b public interface Bar {} // package c import a.Foo import b.Bar extend Foo <: Bar {} // Error
扩展的访问和遮盖
扩展的实例成员与类型定义处一样可以使用 this,this 的功能保持一致。同样也可以省略 this 访问成员。扩展的实例成员不能使用 super。
1 2 3 4 5 6 7 8 9 10 class A { var v = 0 } extend A { func f() { print(this.v) // Ok print(v) // Ok } }
扩展不能访问被扩展类型中 private 修饰的成员。
1 2 3 4 5 6 7 8 9 10 11 class A { private var v1 = 0 protected var v2 = 0 } extend A { func f() { print(v1) // Error print(v2) // Ok } }
扩展不能遮盖被扩展类型的任何成员。
1 2 3 4 5 6 7 class A { func f() {} } extend A { func f() {} // Error }
扩展也不允许遮盖其他扩展增加的任何成员。
1 2 3 4 5 6 7 8 9 class A {} extend A { func f() {} } extend A { func f() {} // Error }
在同一个包内,对同一类型可以扩展多次,并且在扩展中可以直接调用被扩展类型的其他扩展中非 private 修饰的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Foo {} extend Foo { // OK private func f() {} func g() {} } extend Foo { // OK func h() { g() // OK f() // Error } }
扩展泛型类型时,可以使用额外的泛型约束。泛型类型的任意两个扩展之间的可见性规则如下:
如果两个扩展的约束相同,则两个扩展相互可见,即两个扩展内可以直接使用对方内的函数或属性;
如果两个扩展的约束不同,且两个扩展的约束有包含关系,约束更宽松的扩展对约束更严格的扩展可见,反之,不可见;
当两个扩展的约束不同时,且两个约束不存在包含关系,则两个扩展均互相不可见。
示例:假设对同一个类型 E<X> 的两个扩展分别为扩展 1 和扩展 2 ,X 的约束在扩展 1 中比扩展 2 中更严格,那么扩展 1 中的函数和属性对扩展 2 均不可见,反之,扩展 2 中的函数和属性对扩展 1 可见。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 open class A {} class B <: A {} class E<X> {} interface I1 { func f1(): Unit } interface I2 { func f2(): Unit } extend<X> E<X> <: I1 where X <: B { // extension 1 public func f1(): Unit { f2() // OK } } extend<X> E<X> <: I2 where X <: A { // extension 2 public func f2(): Unit { f1() // Error } }
扩展的导入导出
扩展也是可以被导入和导出的,但是扩展本身不能使用可见性修饰符修饰,扩展的导出有一套特殊的规则。
对于直接扩展,当扩展与被扩展的类型在同一个包中,扩展是否导出,由被扩展类型与泛型约束(如果有)的访问修饰符同时决定,当所有的泛型约束都是导出类型(修饰符与导出规则,详见顶层声明的可见性 章节)时,该扩展将被导出。当扩展与被扩展类型不在同一个包中时,该扩展不会导出。
如以下代码所示,Foo 是导出的,f1 函数所在的扩展由于不导出泛型约束,故该扩展不会被导出;f2 和 f3 函数所在的扩展的泛型约束均被导出,故该扩展被导出;f4 函数所在的扩展包含多个泛型约束,且泛型约束中 I1 未被导出,故该扩展不会被导出;f5 函数所在的扩展包含多个泛型约束,所有的泛型约束均是导出的,故该扩展会被导出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 // package a.b package a.b private interface I1 {} internal interface I2 {} protected interface I3 {} extend Int64 <: I1 & I2 & I3 {} public class Foo<T> {} // The extension will not be exported extend<T> Foo<T> where T <: I1 { public func f1() {} } // The extension will be exported, and only packages that import both Foo and I2 will be able to access it. extend<T> Foo<T> where T <: I2 { public func f2() {} } // The extension will be exported, and only packages that import both Foo and I3 will be able to access it. extend<T> Foo<T> where T <: I3 { public func f3() {} } // The extension will not be exported. The I1 with the lowest access level determines the export. extend<T> Foo<T> where T <: I1 & I2 & I3 { public func f4() {} } // The extension is exported. Only the package that imports Foo, I2, and I3 can access the extension. extend<T> Foo<T> where T <: I2 & I3 { public func f5() {} } // package a.c package a.c import a.b.* main() { Foo<Int64>().f1() // Cannot access. Foo<Int64>().f2() // Cannot access. Visible only for sub-pkg. Foo<Int64>().f3() // Ok. Foo<Int64>().f4() // Cannot access. Foo<Int64>().f5() // Cannot access. Visible only for sub-pkg. } // package a.b.d package a.b.d import a.b.* main() { Foo<Int64>().f1() // Cannot access. Foo<Int64>().f2() // Ok. Foo<Int64>().f3() // Ok. Foo<Int64>().f4() // Cannot access. Foo<Int64>().f5() // Ok. }
对于接口扩展则分为两种情况:
当接口扩展与被扩展类型在相同的 package 时,扩展会与被扩展类型以及泛型约束(如果有)一起被导出,不受接口类型的访问级别影响,包外不需要导入接口类型也能访问该扩展的成员。
当接口扩展与被扩展类型在不同的 package 时,接口扩展是否导出由接口类型以及泛型约束(如果有)里用到的类型中最小的访问级别决定。其他 package 必须导入被扩展类型、相应的接口以及约束用到的类型(如果有),才能访问对应接口包含的扩展成员。
如下代码所示,在包 a 中,虽然接口访问修饰符为 private,但 Foo 的扩展仍然会被导出。
1 2 3 4 5 6 7 8 9 // package a package a private interface I0 {} public class Foo<T> {} // The extension is exported. extend<T> Foo<T> <: I0 {}
当在其他包中为 Foo 类型扩展时,扩展是否导出由实现接口和泛型约束的访问修饰符决定。实现接口至少存在一个导出的接口 ,且所有的泛型约束均可导出时,该扩展将被导出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // package b package b import a.Foo private interface I1 {} internal interface I2 {} protected interface I3 {} public interface I4 {} // The extension will not be exported because I1 is not visible outside the file. extend<T> Foo<T> <: I1 {} // The extension is exported. extend<T> Foo<T> <: I2 {} // The extension is exported. extend<T> Foo<T> <: I3 {} // The extension is exported extend<T> Foo<T> <: I1 & I2 & I3 {} // The extension will not be exported. The I1 with the lowest access level determines the export. extend<T> Foo<T> <: I4 where T <: I1 & I2 & I3 {} // The extension is exported. extend<T> Foo<T> <: I4 where T <: I2 & I3 {} // The extension is exported. extend<T> Foo<T> <: I4 & I3 where T <: I2 {}
特别的,接口扩展导出的成员仅限于接口中包含的成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // package a package a public class Foo {} // package b package b import a.Foo public interface I1 { func f1(): Unit } public interface I2 { func f2(): Unit } extend Foo <: I1 & I2 { public func f1(): Unit {} public func f2(): Unit {} public func f3(): Unit {} // f3 will not be exported } // package c package c import a.Foo import b.I1 main() { let x: Foo = Foo() x.f1() // OK, because f1 is a member of I1. x.f2() // error, I2 is not imported x.f3() // error, f3 not found }
与扩展的导出类似,扩展的导入也不需要显式地用 import 导入,扩展的导入只需要导入被扩展的类型、接口和泛型约束,就可以导入可访问的所有扩展。
如下面的代码所示,在 package b 中,只需要导入 Foo 就可以使用 Foo 对应的扩展中的函数 f。
而对于接口扩展,需要同时导入被扩展的类型、扩展的接口和泛型约束 (如果有)才能使用。因此在 package c 中,需要同时导入 Foo 和 I 才能使用对应扩展中的函数 g。
1 2 3 4 5 6 // package a package a public class Foo {} extend Foo { public func f() {} }
1 2 3 4 5 6 7 8 9 10 11 12 // package b package b import a.Foo public interface I { func g(): Unit } extend Foo <: I { public func g() { this.f() // OK } }
1 2 3 4 5 6 7 8 9 10 // package c package c import a.Foo import b.I func test() { let a = Foo() a.f() // OK a.g() // OK }