仓颉-拓展

扩展概述

扩展可以为在当前 package 中可见的类型(除函数、元组、接口)添加新功能。

当不能破坏被扩展类型的封装性,但希望添加额外的功能时,可以使用扩展。

可以添加的功能包括:

  • 添加成员函数
  • 添加操作符重载函数
  • 添加成员属性
  • 实现接口

扩展虽然可以添加额外的功能,但不能变更被扩展类型的封装性,因此扩展不支持以下功能:

  1. 扩展不能增加成员变量。
  2. 扩展的函数和属性必须拥有实现。
  3. 扩展的函数和属性不能使用 openoverrideredef修饰。
  4. 扩展不能访问被扩展类型中 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
}

编译执行上述代码,输出结果为:

1
The size is 0

可以在同一个扩展内同时实现多个接口,多个接口之间使用 & 分开,接口的顺序没有先后关系。

如下面代码所示,可以在扩展中为 Foo 同时实现 I1I2I3

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
}

编译执行上述代码,输出结果为:

1
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)
}

编译执行上述代码,输出结果为:

1
0

当多个接口扩展实现的接口存在继承关系时,扩展将按照“先检查实现父接口的扩展,再检查子接口的扩展”的顺序进行检查。

例如,接口 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()
}

编译执行上述代码,输出结果为:

1
I2 foo

以上例子中,当检查实现 I1 的扩展时,会从 I1 中继承 foo 函数。在检查实现 I2 的扩展时,由于 A 中已存在一个继承的,且签名相同的默认实现 foo ,此时 foo 将被覆盖。因此,调用 Afoo 函数时,最终指向 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

扩展成员可使用的修饰符有:staticpublicprotectedinternalprivatemut

  • 使用 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()
}

扩展内的成员定义不支持使用 openoverrideredef 修饰。

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

扩展的访问和遮盖

扩展的实例成员与类型定义处一样可以使用 thisthis 的功能保持一致。同样也可以省略 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 和扩展 2X 的约束在扩展 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 函数所在的扩展由于不导出泛型约束,故该扩展不会被导出;f2f3 函数所在的扩展的泛型约束均被导出,故该扩展被导出;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.
}

对于接口扩展则分为两种情况:

  1. 当接口扩展与被扩展类型在相同的 package 时,扩展会与被扩展类型以及泛型约束(如果有)一起被导出,不受接口类型的访问级别影响,包外不需要导入接口类型也能访问该扩展的成员。
  2. 当接口扩展与被扩展类型在不同的 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 中,需要同时导入 FooI 才能使用对应扩展中的函数 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
}