函数
函数是一段完成特定任务的独立代码片段,可以通过函数名字来标识,这个名字可以被用来调用函数。仓颉编程语言中函数是一等公民,函数可以赋值给变量,或作为参数传递,或作为返回值返回。
函数定义
仓颉编程语言中,函数的定义遵循以下语法规则:
functionDefinition
: functionModifierList? 'func'
identifier
typeParameters?
functionParameters
(':' type)?
(genericConstraints)?
block
;
可以总结为如下:
- 通过关键字
func
来定义一个函数 func
前可以用函数修饰符进行修饰func
后需要带函数名- 可选的类型形参列表,类型形参列表由
<>
括起,多个类型形参之间用,
分隔 - 函数的参数由
()
括起,多个参数用,
分隔,并且需要给定每个参数的参数类型。详见[函数参数]。 - 可缺省的函数返回类型,由
:type
表示 - 函数定义时必须有函数体,函数体是一个块(不包含函数形参)。
以下示例是一个完整的函数定义具备的所有要素,它没有使用访问修饰符 public
修饰表示其在包内部可访问,函数名为 foo
,有一个 Int64
类型的参数a
,有返回类型Int64
,有函数体。
func foo(a: Int64): Int64 { a }
函数名不能被进行赋值,即函数名不能以表达式左值的形式在程序中出现。
如以下示例中的操作是禁止的。
func f(a: Int64): Int64 { a }
// f = {a: Int64 => a + 1} // compile-time error
函数修饰符
全局函数的修饰符
全局函数可以被所有访问修饰符修饰,默认的可访问性为 internal
。详细内容请参考包和模块管理章节访问修饰符。
局部函数的修饰符
局部函数无可用修饰符。
成员函数的修饰符
类成员函数可用修饰符有:public
, protected
, private
, internal
, static
, open
, override
, redef
详见类的成员以及包和模块管理章节访问修饰符;
接口成员函数可用修饰符有: static
,mut
, 详见[接口成员];
struct 成员函数可用修饰符有:mut
,public
,private
,internal
,static
,详见[struct 类型]。
enum 成员函数可用修饰符有:public
,private
,internal
,static
,详见[enum 类型]。
如果不提供可访问修饰符,接口成员函数以外的成员函数可以在当前包及子包内被访问,接口成员函数默认是 public 语义。
参数
函数定义时参数列表中参数的顺序:非命名参数,命名参数(包括:不带默认值命名参数和带默认值的参数),参数列表的语法定义如下:
functionParameters
: ('(' (unnamedParameterList (',' namedParameterList)? )? ')')
| ('(' (namedParameterList)? ')')
;
nondefaultParameterList
: unnamedParameter (',' unnamedParameter)* (',' namedParameter)*
| namedParameter (',' namedParameter)*
;
namedParameterList
: (namedParameter | defaultParameter) (',' (namedParameter | defaultParameter))*
;
namedParameter
: identifier '!' ':' type
;
defaultParameter
: identifier '!' ':' type '=' expression
;
unnamedParameterList
: unnamedParameter (',' unnamedParameter)*
;
unnamedParameter
: (identifier | '_') ':' type
;
对于非命名参数,可以使用一个 _
代替一个函数体中不会使用到的参数。
func bar(a: Int64 , b!: Float64 = 1.0, s!: String) {} // OK
func bar2(a: Int64 = 1, b!: Float64 = 1.0, s: String = "Hello") {} // Error
func foo(a: Int64, b: Float64)(s: String = "Hello") {} // Error
func f1(a: Int64, _: Int64): Int64 {
return a + 1
}
func f2(_: String): Unit {
print("Hello Cangjie")
}
函数参数均为不可变变量,即均有 let
修饰,在函数定义内不能对其进行赋值。
func foo(a: Int64, b: Float64) {
a = 1 // Error: the parameter 'a' is immutable, and cannot be assigned.
b = 1.0 // Error: the parameter 'b' is immutable, and cannot be assigned.
}
函数的参数类型不受下述“是否是命名形参”、“是否有默认值”的影响;且讨论参数类型时,一个类型与它的 alias 被视为相同的类型。
命名形参
函数定义时通过在形参名后添加 !
来定义命名形参。
namedParameter
: identifier '!' ':' type
;
- 函数定义时,命名形参后不允许有非命名形参;
func add1(a: Int32, b!: Int32): Int32 { a + b } // ok
func add2(a!: Int32, b: Int32): Int32 { a + b } // error
- 当形参被定义成命名形参后,调用这个函数时,则必须在实参值前使用
参数名:
前缀来指定这个实参对应的形参,否则编译报错。
func add(a: Int32, b!: Int32): Int32 { a + b }
add(1, b: 2) // ok
add(1, 2) // error
-
如果抽象函数或 open 修饰的函数有命名形参,那么实现函数或 override 修饰的函数也需要保持同样的命名形参。
open class A { public open func f(a!: Int32): Int32 { return a + 1 } } class B <: A { public override func f(a!: Int32): Int32 { // ok return a + 1 } } class C <: A { public override func f(b!: Int32): Int32 { // error return b + 1 } }
参数的默认值
函数的参数可以有默认值,通过=
来为参数定义默认值。当默认值被定义后,调用这个函数可以忽略这个参数,函数体会使用默认值。为了便于理解,有默认值的参数称为可选参数。
函数定义时,可选参数是一种命名形参,可选参数名后必须添加!
,否则编译报错;
defaultParameter
: identifier '!' ':' type '=' expression
;
如下示例,我们定义了一个add
函数,它的参数类型列表(Int32, Int32)
。函数定义时,b
具有默认值1
。因此,当我们调用add
函数,并且只传递一个值3
时,3
会被赋值给a
,从而返回结果4
。
如果传入两个值3
和2
,那么b
的值为2
。
func add(a: Int32, b!: Int32 = 1): Int32 { a + b }
add(3) // invoke add(3, 1), return 4
add(3, b: 2) // return 5
-
类或接口中被
open
关键字修饰的函数不允许有可选参数。 -
操作符函数不允许有可选参数。
-
匿名函数(lambda 表达式)不允许有可选参数。
-
函数参数默认值中引入的名字从静态作用域中获得,即引入的名字为函数定义时可访问到的名字。
-
函数参数默认值中引入的名字在函数定义时可访问即可,无需和函数本身的可访问性一致。
-
函数参数和其默认值不属于该函数的函数体。
-
参数默认值在函数调用时求值,而非在函数定义时求值。
-
规定函数调用时参数求值顺序是按照定义时顺序从左到右,函数参数的默认值可以引用定义在该参数之前的形参。
-
函数调用时,通过函数名调用可以使用默认值,通过变量名调用不支持使用默认值。
// Compile-time error. // func f(a: Int32, b!: Int32 = 2, c: Int32): Int32 { ... } // OK. func f1(a: Int32, b: Int32, c!: Int32 = 3, d!: Int32 = 4): Int32 { a + b + c + d } func test() { f1(1, 2) // 10, f1(1, 2, 3, 4) f1(1, 2, c: 5) // 12, f1(1, 2, 5, 4) }
/* The names introduced in the default value, does not need to have the same or least restrictive accessibility of the function. */ var x = 10 var y = 10 func g() {} public func f2(a!: Int64 = x * 2 + y, b!: ()->Unit = g) {} // OK. class AAA { static private var x = 10 func f(a!: Int64 = x) { // OK, public method can use private static field. print("${a}") x = x + 10 print("${x}") } }
/* When a function is called, the name in the function declaration can use the default value. When a function is called by using a variable name, arguments cannot be optional. */ func f1(): (Int64) -> Unit { g1 } func g1(a!: Int64 = 42) { print("g1: ${a}") } let gg1 = f1() let x = gg1() // Error, cannot ommit the argument. let gg3 = g1 let a = gg3() // Error, cannot ommit the argument.
由于函数的形参和其默认值不属于该函数的函数体,所以下面例子中的 return 表达式缺少包围它的函数体——它既不属于外层函数 f
(因为内层函数定义 g
已经开始),也不在内层函数 f
的函数体中:
func f() {
func g(x! :Int64 = return) { // Error: return must be used inside a function body
0
}
1
}
函数体
函数体由一个 block 组成。
局部变量
在仓颉编程语言中,允许在函数体内定义变量,将其称为局部变量。
变量可以用var
修饰为可变变量或let
修饰为不可变变量。
func foo(): Unit {
var a = 1
let b = 1
}
嵌套函数
在仓颉编程语言中,允许在函数体内定义函数,将其称为嵌套函数。嵌套函数中可以捕获外部函数中定义的变量或其他嵌套函数。嵌套函数支持递归。
func foo(): Unit {
func add(a: Int32, b: Int32) { a + b }
let c = 1
func nest(): Unit {
print("${c}") // 1
var b = add(1, 2) // b = 3
}
}
函数的返回类型
函数的返回类型有以下情况:
-
任何类型(见第 2 章类型)
-
返回值为元组的函数:可以使用元组类型作为函数的返回类型,将多个值作为一个复合返回值返回。如以下例子,它的返回是一个元组
(a, b)
,返回类型是(Int32, Int32)
func returnAB(a: Int32, b: Int32): (Int32, Int32) { (a, b) }
-
函数类型作为返回类型:可以使用函数类型作为另一个函数的返回类型,如下示例。在
:
后紧跟的是add
函数的类型(Int32, Int32) -> Int32
。func returnAdd(a: Int32, b: Int32): (Int32, Int32) -> Int32 { return {a, b => a + b} // Return a lambda expression. }
如果指定函数的返回类型,则在函数定义的参数列表后使用 :Type
指定,此时要求函数体的类型、函数体中所有 return e
表达式中 e
的类型是标注的类型(Type
)的子类型,否则编译报错。
class A {}
class B <: A {}
// Return is not written.
func add(a: Int32, b: Int32): B {
var c = a + b
if (c > 100) {
return B()
} else {
return A() // Compilation error since A is not a subtype of B.
}
}
特别地,指定返回类型为 Unit 时(如 func f(x:Bool):Unit { x }
),则编译器会在函数体中所有可能返回的地方自动插入表达式 return ()
,使得函数的返回类型总是为 Unit
。示例如下:
func Column(c: (Data) -> Unit): Unit { 2 } // return () is automatically inserted
func Column(c: (Data) -> Unit) { 2 } // return type is Int64
如果不指定函数的返回类型 ,则编译器会根据函数体的类型以及函数体中的所有 return
表达式来共同推导出函数的返回类型。此过程不是完备的,如遇到(互)递归函数而无法推导它们的返回类型时,编译报错。(注意:不能为没有函数体的函数推导其返回类型。)
函数的返回类型推导规则如下:函数体是表达式和声明的序列,我们将序列的最后一项的类型记为 T0
(若块的最后一项是表达式,则为表达式的类型;若最后一项为声明,则 T0 = Unit
),再将函数体中所有 return e
(包括所有子表达式中的 return e
)表达式中 e
的类型记为 T1 ... Tn
,则函数的返回类型是 T0, T1, ..., Tn
的最小公共父类型。如果不存在最小公共父类型,则产生一个编译错误。示例如下:
open class Base {}
class Child <: Base {}
func f(a: Rune) {
if (false) {
return Base()
}
return Child()
}
- 函数体的类型是块的最后一项的类型,即
return Child()
的类型,其类型为Nothing
。 - 第一个
return e
表达式return Base()
中e
的类型是Base
。 - 第二个
return e
表达式return Child()
中e
的类型为Child
。 - 由于
Nothing
,Base
,Child
三者的最小公共父类型是Base
,所以该函数的返回类型为Base
。
注:函数的返回值具有 let
修饰的语义。
函数声明
仓颉编程语言中,函数声明和函数定义的区别是,前者没有函数体。函数声明的语法如下:
functionDeclaration
: functionModifierList? 'func'
identifier
typeParameters?
functionParameters
(':' type)?
genericConstraints?
;
函数声明可以出现在抽象类,接口中。
函数重定义
对于非泛型函数,在同一个作用域中,参数类型完全相同的同名函数被视为重定义,将产生一个编译错误。以下几种情况要格外注意:
- 同名函数即使返回类型不同也构成重定义。
- 同名的泛型与非泛型函数永不构成重定义。
- 在继承时,对于子类中的一个与父类同名且参数类型完全相同的函数,若其返回类型是父类中的版本的子类型,则构成覆盖,也不构成重定义。(这是因为子类与父类作用域不同。)
对于两个泛型函数,如果重命名一个函数的泛型形参后,其非泛型部分与另一个函数的非泛型部分构成重定义,则这两个泛型函数构成重定义。举例如下:
-
下面这两个泛型函数构成重定义,因为存在一种
[T1 |-> T2]
的重命名(作用到第一个函数上),使得两个函数的非泛型部分构成重定义。func f<T1>(a: T1) {} func f<T2>(b: T2) {}
-
下面这两个泛型函数不构成重定义,因为找不到上述的一种重命名。
func f<X,Y>(a:X, b:Y) {} func f<Y,X>(a:X, b:Y) {}
-
下面的这两个泛型函数构成重定义,因为存在一种
[X |-> Y, Y |-> X]
的重命名使得两个函数的非泛型部分构成重定义。func f<X,Y>(a:X, b:Y) {} func f<Y,X>(a:Y, b:X) {}
函数类型
函数类型由函数的参数类型和返回类型组成,其中参数类型与返回类型之间用->
分隔。
functionType:
: '(' (type (, type)*)? ')' '->' type
以下是一些示例:
-
示例 1:没有参数、返回类型为
Unit
func hello(): Unit { print("Hello!") } // function type: () -> Unit
-
示例 2:参数类型为
Int32
,返回类型为Unit
func display(a: Int32): Unit { print("${a}") } // function type: (Int32) -> Unit
-
示例 3:两个参数类型均为
Int32
,返回类型为Int32
func add(a: Int32, b: Int32): Int32 { a + b } // function type: (Int32, Int32) -> Int32
-
示例 4:参数类型为
(Int32, Int32) -> Int32
,Int32
和Int32
,返回类型为Unit
func printAdd(add: (Int32, Int32) -> Int32, a: Int32, b: Int32): Unit { print("${add(a, b)}") } // function type: ((Int32, Int32) -> Int32, Int32, Int32) -> Unit
-
示例 5:两个参数类型均为
Int32
,返回类型为函数类型(Int32, Int32) -> Int32
func returnAdd(a: Int32, b: Int32): (Int32, Int32) -> Int32 { {a, b => a + b} } // function type: (Int32, Int32) -> (Int32, Int32) -> Int32
-
示例 6:两个参数类型均为
Int32
, 返回为一个元组类型为:(Int32, Int32)
。func returnAB(a: Int32, b: Int32): (Int32, Int32) { (a, b) } // function type: (Int32, Int32) -> (Int32, Int32)
函数调用
有关函数调用表达式的语法,请参考 [函数调用表达式]。
命名实参
命名实参是指在函数调用时,在实参值前使用 形参名 :
前缀来指定这个实参对应的形参。
只有在函数定义时使用!
定义的命名形参,才可以在调用时使用命名实参的语法。
函数调用时,所有的命名形参均必须使用命名实参来传参,否则报错。
在函数调用表达式中,命名实参后不允许出现非命名实参。使用命名实参指定实参值时,可以不需要和形参列表的顺序保持一致。
func add(a!: Int32, b!: Int32): Int32 {
a + b
}
var sum1 = add(1, 2) // error
var sum2 = add(a: 1, b: 2) // OK, 3
var sum3 = add(b: 2, a: 1) // OK, 3
函数调用类型检查
本节介绍的是,给定一个调用表达式,对调用表达式所需要进行的类型检查。如果被调用的函数涉及重载,则需要根据[函数重载]的规则进行类型检查和重载决议。
1、如果函数调用表达式中指定了类型参数,只有类型实参的个数与类型形参的个数相同才可能通过类型检查,即假设函数调用表达式为:f<T1, ..., Tm>(A1, ..., An)
,其中给定了 m 个类型实参,则函数类型形参数量须为 m;
open class Base {}
class Sub <: Base {}
func f<X, Y>(a: X, b: Y) {} // f1
func f<X>(a: Base, b: X) {} // f2
f<Base>(Base(), Sub()) // f2 may pass the type checking
2、根据调用表达式中的实参和调用表达式所在的类型检查上下文中指定的返回类型R
对函数进行类型检查。
假设函数定义为:
$$
f_i<T_{i1},...,T_{ip}>(A_{i1}, ..., A_{ik}): R_i \ \ where \ \ C_{i1}, ..., C_{iq_i}
$$
1) 如果调用表达式带了类型实参:fi<T1, ..., Tp>(A1, ..., Ak)
,那么对于函数fi
的类型检查规则如下:
a) 类型实参约束检查:类型实参 `<T1, ..., Tp>` 需要满足函数 `fi` 的类型约束。
$$
\sigma = [T_1 \mapsto T_{i1}, ..., T_p \mapsto T_{ip}]
$$
$$
\Delta \vdash \sigma \ \ solves \ \ C_{i1}, ..., C_{iq_i}
$$
b) 参数类型检查:将类型实参代入函数 fi
的形参后,满足实参类型 (A1, ..., Ak)
是类型实参代入形参后类型的子类型。
$$
\sigma = [T_1 \mapsto T_{i1}, ..., T_p \mapsto T_{ip}]
$$
$$
\Delta \vdash (A1, ..., Ak) <: \sigma (A_{i1},...,A_{ik})
$$
c) 返回类型检查:如果调用表达式的上下文对其有明确类型要求R
,则需要根据返回类型进行类型检查,将类型实参代入函数 fi
的返回类型 Ri
后,满足类型实参代入后的返回类型是 R
的子类型。
$$
\sigma = [T_1 \mapsto T_{i1}, ..., T_p \mapsto T_{ip}]
$$
$$
\Delta \vdash \sigma R_i <: R
$$
2)如果调用表达式不带类型实参:f(A1, ..., An)
,那么对于函数fi
的类型检查规则如下:
a) 如果`fi`是非泛型函数,则按如下规则进行类型检查:
i. 参数类型检查:实参类型 `(A1, ..., Ak)` 是形参类型的子类型。
$$
\Delta \vdash (A1, ..., Ak) <: (A_{i1},...,A_{ik})
$$
ii. 返回类型检查:如果调用表达式的上下文对其有明确类型要求 R
,则检查函数 fi
的返回类型 Ri
是 R
的子类型。
$$ \Delta \vdash R_i <: R $$
open class Base {}
class Sub <: Base {}
func f(a: Sub) {1} // f1
func f(a: Base) {Base()} // f2
let x: Base = f(Sub()) // f2 can pass the type checking
b) 如果`fi`是泛型函数,则按如下规则进行类型检查 :
i. 参数类型检查:存在代换使得实参类型`(A1, ..., Ak)`是形参类型代换后的类型的子类型。
$$
\sigma = [T_1 \mapsto T_{i1}, ..., T_p \mapsto T_{ip}]
$$
$$
\Delta \vdash (A1, ..., Ak) <: \sigma (A_{i1},...,A_{ik})
$$
ii. 返回类型检查:如果调用表达式的上下文对其有明确类型要求R
,则需要根据返回类型进行类型检查,将 i) 中的代换代入函数fi
的返回类型Ri
后,满足代换后的返回类型是R
的子类型。
$$ \sigma = [T_1 \mapsto T_{i1}, ..., T_p \mapsto T_{ip}] $$ $$ \Delta \vdash \sigma R_i <: R $$
需要注意的是:
- 如果函数有缺省值,在类型检查时会补齐缺省值之后再进行类型检查;
- 如果函数有命名参数,命名参数的顺序可能会和形参顺序不一致,在类型检查时,命名实参要与名字匹配的命名形参对应。
尾随 Lambda
当函数最后一个形参是函数类型,并且函数调用对应的实参是 lambda 时,我们可以使用尾随 lambda 语法,将 lambda 放在函数调用的尾部,括号外面。
func f(a: Int64, fn: (Int64)->Int64) { fn(a) }
f(1, { i => i * i }) // normal function call
f(1) { i => i * i } // trailing lambda
func g(a!: Int64, fn!: (Int64)->Int64) { fn(a) }
g(a: 1, fn: { i => i * i }) // normal function call
g(a: 1) { i => i * i } // trailing lambda
当函数调用有且只有一个 lambda 实参时,我们还可以省略 ()
,只写 lambda。
func f(fn: (Int64)->Int64) { fn(1) }
f{ i => i * i }
如果尾随 lambda 不包含形参,=>
可以省略。
func f(fn: ()->Int64) { fn() }
f{ i * i }
需要注意的是,尾随 lambda 语法只能用在具有 函数名/变量名
的函数调用上,并且尾随 lambda 的 lambda 表达式只会解释为 函数名/变量名
对应的函数的参数。这意味着以下两种调用例子是不合法的:
func f(): (()->Unit)->Unit {
{a => }
}
f() {} // error, the lambda expression is not argument for f.
func g(a: ()->Unit) {}
if (true) { g } else { g } () {} // error, illegal trailing lambda syntax
必须先将以上的表达式赋值给变量,使用变量名调用时才可以使用尾随 lambda 语法。如下面的代码所示:
let f2 = f()
f2 {} // ok
let g2 = if (true) { g } else { g }
g2() {} // ok
普通函数调用和构造函数调用都可以使用这个语法,包含 this()
和 super()
。
this(1, { i => i * i } )
this(1) { i => i * i }
super(1, { i => i * i } )
super(1) { i => i * i }
变长参数
变长参数是一种特殊的函数调用语法糖,当形参最后一个非命名参数是 Array
类型时,实参中对应位置可以直接传入参数序列代替 Array
字面量。
- 变长参数没有特殊的声明语法,只要求函数声明处最后一个非命名参数是
Array
类型。 - 变长参数在函数调用时可以使用普通参数列表的形式逐个传入
Array
的多个元素。 - 非命名参数中,只有最后一个位置的参数可以使用变长参数。命名参数不能使用这个语法糖。
- 变长参数对全局函数、静态成员函数、实例成员函数、局部函数、构造函数、函数变量、lambda、函数调用操作符重载、索引操作符重载的调用都适用,不支持其它操作符重载、compose、pipeline 这几种调用方式。
- 变长参数的个数可以是 0 个或以上。
- 变长参数只有在函数重载所有情况都不匹配的情况下,才判断是否可以应用语法糖,优先级最低。
func f1(arr: Array<Int64>) {}
func f2(a: Int64, arr: Array<Int64>) {}
func f3(arr: Array<Int64>, a: Int64) {}
func f4(arr1!: Array<Int64>, a!: Int64, arr2!: Array<Int64>) {}
func g() {
let li = [1, 2, 3]
f1(li)
f1(1, 2, 3) // using variable length argument
f1() // using variable length argument
f2(4, li)
f2(4, 1, 2, 3) // using variable length argument
f3(1, 2, 3) // error, Array is not the last parameter
f4(arr1: 1,2,3, a: 2, arr2: 1,2,3) // error, named parameters cannot use variable length argument
}
函数重载决议总是会优先考虑不使用变长参数就能匹配的函数,只有在所有函数都不能匹配,才尝试使用变长参数解析。
当编译器无法决议时会报错。
open class A {
func f(v: Int64): Unit { // f1
}
}
class B <: A {
func f(v: Array<Int64>): Unit { // f2
}
}
func p1() {
let x = B()
x.f(1) // call the f1
}
func g<T>(arg: T): Unit { // g1
}
func g(arg: Array<Int64>): Unit { // g2
}
func p2() {
g(1) // call the g1
}
func h(arg: Any): Unit { // h1
}
func h(arg: Array<Int64>): Unit { // h2
}
func p3() {
h(1) // call the h1
}
函数作用域
在仓颉编程语言中,函数可以在源程序顶层定义或者在函数内部定义:
-
全局函数
在源程序顶层定义函数称为全局函数,它的作用域是全局的。如下示例,函数
globalFunction
是全局函数。它的作用域是全局作用域。func globalFunction() {}
-
嵌套函数
在函数体内部定义的函数成为嵌套函数,它的作用域是局部的,详见[作用域]。如下示例,函数
nestedFunction
是嵌套函数,它的作用域是从定义之后开始,到globalFunction
函数体结束。func globalFunction() { func nestedFunction() {} }
-
成员函数
在类型定义中可以声明或定义成员函数。成员函数的作用域是整个类型及其扩展。
interface Myinterface { func foo(): Unit static func bar(): Unit } class MyClass { func foo() {} static func bar() {} }
-
扩展成员函数
在扩展中可以声明额外的成员函数。它的作用域是被扩展类型的所有扩展,同时受访问修饰符限制。
extend MyType { func foo(): Unit {} }
Lambda 表达式
一个 Lambda 表达式是一个匿名的函数。
Lambda 表达式的语法如下:
lambdaExpression
: '{' NL* lambdaParameters? '=>' NL* expressionOrDeclarations '}'
;
lambdaParameters
: lambdaParameter (',' lambdaParameter)* ','?
;
lambdaParameter
: (identifier | '_') (':' type)?
;
lambda 表达式有两种形式,一种是有形参的 {a: Int64 => e1; e2 }
,另一种是无形参的 { => e1; e2 }
(e1 和 e2 是表达式或声明序列)。
let f1: (Int64, Int64)->Int64 = {a: Int64, b: Int64 => a + b}
var f2: () -> Int32 = { => 123 }
对于有形参的 lambda 表达式,可以使用一个 _
代替一个形参,_
代表在 lambda 的函数体中不会使用
到的参数。
let f2 = {n: Int64, _: Int64 => return n * n}
let f3: (Int32, Int32) -> Int32 = {n, _ => return n * n}
let f4: (String) -> String = {_ => return "Hello"}
Lambda 表达式中不支持声明返回类型。
-
若上下文明确指定了 lambda 表达式的返回类型,则其返回类型为上下文指定的类型。如果指定的返回类型是
Unit
,则仓颉编译器还会在 lambda 表达式的函数体中所有可能返回的地方插入return ()
,使其总是返回Unit
类型。指定了Unit
返回类型的示例如下:func column(c: (Data) -> Unit) {...} func row(r: (Data) -> Unit) {...} func build():Unit { column { _ => row { _ => buildDetail() buildCalendar() } // OK. Well typed since 'return' is inserted. width(750) height(700) backgroundColor("#ff41444b") } // OK. Well typed since 'return' is inserted. }
-
若不存在这样的上下文,则
=>
右侧的表达式的类型会被视为 lambda 表达式的返回类型。同函数一样,若无法推导出返回类型,则会编译报错。
Lambda 表达式中参数的类型标注可缺省,编译器会尝试从上下文进行类型推断,当编译器无法推断出类型时会编译报错。
var sum1: (Int32, Int32) -> Int32 = {a, b => a + b}
var sum2: (Int32, Int32) -> Int32 = {a: Int32, b => a + b}
var display = { => print("Hello") }
var a = { => return 1 }
=>
右侧的内容与普通函数体的规则一样,同样可以省略 return。若 =>
的右侧为空,返回值为 ()
。
sum1 = {a, b => a + b}
sum2 = {a, b => return a + b} // Same as that in the previous line.
Lambda 表达式支持原地调用,例如:
let r1 = {a: Int64, b: Int64 => a + b}(1, 2) // r1 = 3
let r2 = { => 123 }() // r2 = 123
闭包
闭包指的是自包含的函数或 lambda,闭包可以从定义它的静态作用域中捕获变量,即使对闭包调用不在定义的作用域,仍可访问其捕获的变量。变量捕获发生在闭包定义时。
不是所有闭包内的变量访问都称为捕获,以下情形的变量访问不是捕获:
- 对定义在本函数或本 lambda 内的局部变量的访问;
- 对本函数或本 lambda 的形参的访问;
- 全局变量和静态成员变量在函数或 lambda 中的访问;
- 实例成员变量在实例成员函数中的访问。由于实例成员函数将
this
作为参数传入,在实例成员函数内通过this
访问所有实例成员变量。
关于变量的捕获,可以分为以下两种情形:
- 捕获
let
声明的变量:在闭包中不能修改这些变量的值。如果捕获的变量是引用类型,可修改其可变实例成员变量的值。仅捕获了let
声明的局部变量的函数或 lambda 可以作为一等公民使用,即可以赋值给变量,可以作为实参或返回值使用,可以作为表达式使用。 - 捕获
var
声明的变量:捕获了可变变量的函数或 lambda 只能被调用,不能作为一等公民使用,包括不能赋值给变量,不能作为实参或返回值使用,不能作为表达式使用。
需要注意的是,捕获具有传递性,如果一个函数
f
调用了捕获var
变量的函数g
,且存在g
捕获的var
变量不在函数f
内定义,那么函数f
同样捕获了var
变量,此时,f
也不能作为一等公民使用。
以下示例中,h
仅捕获了let
声明的变量y
,h
可以作为一等公民使用。
func f(){
let y = 2
func h() {
print(y) // OK, captured an immutable variable.
}
let d = h // OK, h can be assigned to variable
return h // OK, h can be a return value
}
以下示例中,g
捕获了var
声明的变量x
,g
不可以作为一等公民使用,仅能被调用。
func f() {
var x = 1
func g() {
print(x) // OK, captured a mutable variable.
}
let b = g // Error, g cannot be assigned to a variable
g // Error, g cannot be used as an expression
g() // OK, g can be invoked
// Lambda captured a mutable variable, cannot be assigned to a variable
let e = { => print("${x}") } // Error
let i = { => x*x }() // OK, lambda captured a mutable variable, can be invoked.
return g // Error, g cannot be used as a return value.
}
以下示例中,g
捕获了var
声明的变量x
,f
调用了g
,且g
捕获的x
不在f
内定义,f
同样不能作为一等公民使用:
func h(){
var x = 1
func g() { x } // captured a mutable variable
func f() {
g() // invoked g
}
return f // error
}
以下示例中,g
捕获了var
声明的变量x
,f
调用了g
。但g
捕获的x
在f
内定义,f
没有捕获其它var
声明的变量。因此,f
仍作为一等公民使用:
func h(){
func f() {
var x = 1
func g() { x } // captured a mutable variable
g()
}
return f // ok
}
访问了var
修饰的全局变量、静态成员变量、实例成员变量的函数或 lambda 仍可作为一等公民使用。
class C {
static var a: Int32 = 0
static func foo() {
a++ // OK
return a
}
}
var globalV1 = 0
func countGlobalV1() {
globalV1++
C.a = 99
let g = C.foo // OK
}
func g(){
let f = countGlobalV1 // OK
f()
}
捕获的变量必须满足以下规则:
- 被捕获的变量必须在闭包定义时可见,否则编译报错;
- 变量被捕获时必须已经完成初始化,否则编译报错;
- 如果函数外有变量,同时函数内有同名的局部变量,函数内的闭包因局部变量的作用域未开始而捕获了函数外的变量时,为避免用户误用,报 warning;
// 1. The captured variable must be defined before the closure.
let x = 4
func f() {
print("${x}") // Print 4.
let x = 99
func f1() {
print("${x}")
}
let f2 = { =>
print("${x}")
}
f1() // Print 99.
f2() // Print 99.
}
// 2. The variable must be initialized before being captured.
let x = 4
func f() {
print("${x}") // Print 4.
let x: Int64
func f1() {
print("${x}") // Error: x is not initialized yet.
}
x = 99
f1()
}
// 3. If there is a local variable in a block, closures capture variables of the same name in the outer scope will report a warning.
let x = 4
func f() {
print("${x}") // Print 4.
func f1() {
print("${x}") // warning
}
let f2 = { =>
print("${x}") // warning
}
let x = 99
f1() // print 4
f2() // print 4
}
函数重载
函数重载定义
在仓颉编程语言中,一个作用域中可见的同一个函数名对应的函数定义不构成重定义时便构成重载。函数存在重载时,在进行函数调用时需要根据函数调用表达式中的实参的类型和上下文信息明确是哪一个函数定义被使用。
被重载的多个函数必须遵循以下规则:
-
函数名必须相同,且满足以下情况之一:
- 通过
func
关键字引入的函数名。 - 在一个 class 或 struct 内定义的构造函数,包括主构造函数和
init
构造函数。
- 通过
-
参数类型必须不同,即满足以下情况之一:
-
函数参数数量不同。
-
函数参数数量相同,但是对应位置的参数类型不同。
-
-
必须在同一个作用域中可见
注意,参数类型不受“是否是命名形参”、“是否有默认值”影响;且讨论参数类型时,一个类型与它的 alias 被视为相同的类型。例如下面例子中的四个函数的参数类型是完全相同的:
type Boolean = Bool
func f(a : Bool) {}
func f(a! : Bool) {}
func f(a! : Bool = false) {}
func f(a! : Boolean) {}
示例一:定义源文件顶层的 2 个函数的参数类型不同,f
构成了重载。
// f overloading
func f() {...}
func f(a: Int32) {...}
示例二:接口I
、类C1
、C2
中的函数f1
构成了重载。
interface I {
func f1() {...}
}
open class C1 {
func f1(a: Int32) {...}
}
class C2 <: C1 & I {
// f1 overloading
func f1(a: Int32, b: String) {...}
}
示例三:类型内的构造函数之间构成重载。
class C {
var name: String = "abc"
// constructor overloading
init(){
print(name)
}
init(name: String){
this.name = name
}
}
示例四:如下示例,函数参数数量相同,相同位置的类型相同,仅参数类型中包含的类型变元的约束不同,不构成重载
interface I1{}
interface I2{}
func f<T>(a: T) where T <: I1 {}
func f<T>(a: T) where T <: I2 {} // Error, not overloading
mut 函数
mut
函数是一种特殊的实例成员函数。在 struct 类型的 mut
成员函数中,可以通过 this
修改成员变量,而在 struct 类型的非 mut
成员函数中,不可以通过 this
修改成员变量。
定义
mut
函数使用 mut
关键字修饰,只允许在 interface、struct 和 struct 扩展中定义,并且只能作用于实例成员函数(不支持静态成员函数)。
struct A {
mut func f(): Unit {} // ok
mut static func g(): Unit {} // error
mut operator func +(rhs: A): A { // ok
return A()
}
}
extend A {
mut func h(): Unit {} // ok
}
class B {
mut func f(): Unit {} // error
}
interface I {
mut func f(): Unit // ok
}
在 mut 函数中可以对 struct 实例的字段进行赋值,这些赋值会修改实例并立刻生效。与实例成员函数相同,this
不是必须的,可以由编译器推断。
struct Foo {
var i = 0
mut func f() {
this.i += 1 // ok
i += 1 // ok
}
}
main() {
var a = Foo()
print(a.i) // 0
a.f()
print(a.i) // 2
a.f()
print(a.i) // 4
return 0
}
mut
函数中的 this
具有额外的限制:
- 不能被捕获(意味着当前实例的字段也不能被捕获);
- 不能作为表达式。
struct Foo {
var i = 0
mut func f(): Foo {
let f1 = { => this } // error
let f2 = { => this.i = 2 } // error
let f3 = { => this.i } // error
let f4 = { => i } // error
return this // error
}
}
接口中的 mut 函数
struct 类型在实现 interface 的函数时必须保持一样的 mut
修饰。struct 以外的类型实现 interface 的函数时禁止使用 mut
修饰。
interface I {
mut func f1(): Unit
func f2(): Unit
}
struct A <: I {
public mut func f1(): Unit {} // ok
public func f2(): Unit {} // ok
}
struct B <: I {
public func f1(): Unit {} // error
public mut func f2(): Unit {} // error
}
class C <: I {
public func f1(): Unit {} // ok
public func f2(): Unit {} // ok
}
需要注意的是,当 struct 的实例赋值给 interface 类型时是拷贝语义,因此 interface 的 mut 函数并不能修改原本 struct 实例的值。
interface I {
mut func f(): Unit
}
struct Foo <: I {
var v = 0
public mut func f(): Unit {
v += 1
}
}
main() {
var a = Foo()
var b: I = a
b.f()
print(a.v) // 0
return 0
}
访问规则
如果一个变量使用 let
声明,并且类型可能是 struct(包含静态类型是 struct 类型,或者类型变元可能是 struct 类型),那么这个变量不能访问该类型使用 mut
修饰的函数。其它情况均允许访问。
interface I {
mut func f(): Unit
}
struct Foo <: I {
var i = 0
public mut func f(): Unit {
i += 1
}
}
class Bar <: I {
var i = 0
public func f(): Unit {
i += 1
}
}
main() {
let a = Foo()
a.f() // error
var b = Foo()
b.f() // ok
let c: I = Foo()
c.f() // ok
return 0
}
func g1<T>(v: T): Unit where T <: I {
v.f() // error
}
func g2<T>(v: T): Unit where T <: Bar & I {
v.f() // ok
}
如果一个变量的类型可能是 struct(包含静态类型是 struct 类型,或者类型变元可能是 struct 类型),那么这个变量不能将该类型使用 mut
修饰的函数被作为高阶函数使用,只能调用这些 mut 函数。
interface I {
mut func f(): Unit
}
struct Foo <: I {
var i = 0
public mut func f(): Unit {
i += 1
}
}
class Bar <: I {
var i = 0
public func f(): Unit {
i += 1
}
}
main() {
var a = Foo()
var fn = a.f // error
var b: I = Foo()
fn = b.f // ok
return 0
}
func g1<T>(v: T): Unit where T <: I {
let fn = v.f // error
}
func g2<T>(v: T): Unit where T <: Bar & I {
let fn = v.f // ok
}
非 mut 的实例成员函数(包括 lambda 表达式)不能访问 this
的 mut 函数,反之可以。
struct Foo {
var i = 0
mut func f(): Unit {
i += 1
g() // ok
}
func g(): Unit {
f() // error
}
}
interface I {
mut func f(): Unit {
g() // ok
}
func g(): Unit {
f() // error
}
}