仓颉高级应用与实践 - Lambda 表达式 Lambda 表达式
Lambda 表达式是一种匿名函数(即没有函数名的函数),其核心设计目的是在程序中快速定义简短的函数逻辑,无需显式声明函数名称。这一概念起源于数学中的 λ 演算(lambda calculus),后被引入多种编程语言(如 C++、Python、C# 等),用于简化代码并提升灵活性。仓颉编程语言中也引入了 Lambda 表达式,具体使用介绍将在本小节展开介绍。Lambda 表达式的语法为如下形式:
Lambda 表达式
Lambda 表达式定义
Lambda 表达式是一种匿名函数(即没有函数名的函数),其核心设计目的是在程序中快速定义简短的函数逻辑,无需显式声明函数名称。这一概念起源于数学中的 λ 演算(lambda calculus),后被引入多种编程语言(如 C++、Python、C# 等),用于简化代码并提升灵活性。仓颉编程语言中也引入了 Lambda 表达式,具体使用介绍将在本小节展开介绍。
Lambda 表达式的语法为如下形式: { p1: T1, …, pn: Tn => expressions | declarations }。
其中,=> 之前为参数列表,多个参数之间使用 , 分隔,每个参数名和参数类型之间使用 : 分隔。=> 之前也可以没有参数。=> 之后为 Lambda 表达式体,是一组表达式或声明序列。Lambda 表达式的参数名的作用域与函数的相同,为 Lambda 表达式的函数体部分,其作用域级别可视为与 Lambda 表达式的函数体内定义的变量等同。
let f1 = { a: Int64, b: Int64 => a + b }
var display = { => // Parameterless lambda expression.
println("Hello")
println("World")
}
Lambda 表达式不管有没有参数,都不可以省略 =>,除非其作为尾随 lambda。例如:
var display = { => println("Hello") }
func f2(lam: () -> Unit) {}
let f2Res = f2 { println("World") } // OK to omit the =>
Lambda 表达式中参数的类型标注可缺省。以下情形中,若参数类型省略,编译器会尝试进行类型推断,当编译器无法推断出类型时会编译报错:
Lambda 表达式赋值给变量时,其参数类型根据变量类型推断;
Lambda 表达式作为函数调用表达式的实参使用时,其参数类型根据函数的形参类型推断。
// The parameter types are inferred from the type of the variable sum1
var sum1: (Int64, Int64) -> Int64 = { a, b => a + b }
var sum2: (Int64, Int64) -> Int64 = { a: Int64, b => a + b }
func f(a1: (Int64) -> Int64): Int64 {
a1(1)
}
main(): Int64 {
// The parameter type of lambda is inferred from the type of function f
f({ a2 => a2 + 10 })
}
Lambda 表达式中不支持声明返回类型,其返回类型总是从上下文中推断出来,若无法推断则报错。
若上下文明确指定了 Lambda 表达式的返回类型,则其返回类型为上下文指定的类型。
Lambda 表达式赋值给变量时,其返回类型根据变量类型推断返回类型:
let f: () -> Unit = { ... }
Lambda 表达式作为参数使用时,其返回类型根据使用处所在的函数调用的形参类型推断:
func f(a1: (Int64) -> Int64): Int64 {
a1(1)
}
main(): Int64 {
f({ a2: Int64 => a2 + 10 })
}
Lambda 表达式作为返回值使用时,其返回类型根据使用处所在函数的返回类型推断:
func f(): (Int64) -> Int64 {
{ a: Int64 => a }
}
若上下文中类型未明确,与推导函数的返回值类型类似,编译器会根据 Lambda 表达式体中所有 return 表达式 return xxx 中 xxx 的类型,以及 Lambda 表达式体的类型,来共同推导出 Lambda 表达式的返回类型。
=> 右侧的内容与普通函数体的规则一样,返回类型为 Int64:
let sum1 = { a: Int64, b: Int64 => a + b }
=> 的右侧为空,返回类型为 Unit:
let f = { => }
Lambda 表达式
Lambda 表达式定义
Lambda 表达式是一种匿名函数(即没有函数名的函数),其核心设计目的是在程序中快速定义简短的函数逻辑,无需显式声明函数名称。这一概念起源于数学中的 λ 演算(lambda calculus),后被引入多种编程语言(如 C++、Python、C# 等),用于简化代码并提升灵活性。仓颉编程语言中也引入了 Lambda 表达式,具体使用介绍将在本小节展开介绍。
Lambda 表达式的语法为如下形式: { p1: T1, …, pn: Tn => expressions | declarations }。
其中,=> 之前为参数列表,多个参数之间使用 , 分隔,每个参数名和参数类型之间使用 : 分隔。=> 之前也可以没有参数。=> 之后为 Lambda 表达式体,是一组表达式或声明序列。Lambda 表达式的参数名的作用域与函数的相同,为 Lambda 表达式的函数体部分,其作用域级别可视为与 Lambda 表达式的函数体内定义的变量等同。
let f1 = { a: Int64, b: Int64 => a + b }
var display = { => // Parameterless lambda expression.
println(“Hello”)
println(“World”)
}
Lambda 表达式不管有没有参数,都不可以省略 =>,除非其作为尾随 lambda。例如:
var display = { => println(“Hello”) }
func f2(lam: () -> Unit) {}
let f2Res = f2 { println(“World”) } // OK to omit the =>
Lambda 表达式中参数的类型标注可缺省。以下情形中,若参数类型省略,编译器会尝试进行类型推断,当编译器无法推断出类型时会编译报错:
Lambda 表达式赋值给变量时,其参数类型根据变量类型推断;
Lambda 表达式作为函数调用表达式的实参使用时,其参数类型根据函数的形参类型推断。
// The parameter types are inferred from the type of the variable sum1
var sum1: (Int64, Int64) -> Int64 = { a, b => a + b }
var sum2: (Int64, Int64) -> Int64 = { a: Int64, b => a + b }
func f(a1: (Int64) -> Int64): Int64 {
a1(1)
}
main(): Int64 {
// The parameter type of lambda is inferred from the type of function f
f({ a2 => a2 + 10 })
}
Lambda 表达式中不支持声明返回类型,其返回类型总是从上下文中推断出来,若无法推断则报错。
若上下文明确指定了 Lambda 表达式的返回类型,则其返回类型为上下文指定的类型。
Lambda 表达式赋值给变量时,其返回类型根据变量类型推断返回类型:
let f: () -> Unit = { … }
Lambda 表达式作为参数使用时,其返回类型根据使用处所在的函数调用的形参类型推断:
func f(a1: (Int64) -> Int64): Int64 {
a1(1)
}
main(): Int64 {
f({ a2: Int64 => a2 + 10 })
}
Lambda 表达式作为返回值使用时,其返回类型根据使用处所在函数的返回类型推断:
func f(): (Int64) -> Int64 {
{ a: Int64 => a }
}
若上下文中类型未明确,与推导函数的返回值类型类似,编译器会根据 Lambda 表达式体中所有 return 表达式 return xxx 中 xxx 的类型,以及 Lambda 表达式体的类型,来共同推导出 Lambda 表达式的返回类型。
=> 右侧的内容与普通函数体的规则一样,返回类型为 Int64:
let sum1 = { a: Int64, b: Int64 => a + b }
=> 的右侧为空,返回类型为 Unit:
let f = { => }
Lambda 表达式调用
Lambda 表达式支持立即调用,例如:
let r1 = { a: Int64, b: Int64 => a + b }(1, 2) // r1 = 3
let r2 = { => 123 }() // r2 = 123
Lambda 表达式也可以赋值给一个变量,使用变量名进行调用,例如:
func f() {
var g = { x: Int64 => println("x = ${x}") }
g(2)
}
闭包
一个函数或 lambda 从定义它的静态作用域中捕获了变量,函数或 lambda 和捕获的变量一起被称为一个闭包,这样即使脱离了闭包定义所在的作用域,闭包也能正常运行。
函数或 lambda 的定义中对于以下几种变量的访问,称为变量捕获:
函数的参数缺省值中访问了本函数之外定义的局部变量;
函数或 lambda 内访问了本函数或本 lambda 之外定义的局部变量;
class/struct 内定义的不是成员函数的函数或 lambda 访问了实例成员变量或 this。
以下情形的变量访问不是变量捕获:
对定义在本函数或本 lambda 内的局部变量的访问;
对本函数或本 lambda 的形参的访问;
对全局变量和静态成员变量的访问;
对实例成员变量在实例成员函数或属性中的访问。由于实例成员函数或属性将 this 作为参数传入,在实例成员函数或属性内通过 this 访问所有实例成员变量。
变量的捕获发生在闭包定义时,因此变量捕获有以下规则:
被捕获的变量必须在闭包定义时可见,否则编译报错;
被捕获的变量必须在闭包定义时已经完成初始化,否则编译报错。
示例 1:闭包 add,捕获了 let 声明的局部变量 num,之后通过返回值返回到 num 定义的作用域之外,调用 add 时仍可正常访问 num。
func returnAddNum(): (Int64) -> Int64 {
let num: Int64 = 10
func add(a: Int64) {
return a + num
}
add
}
main() {
let f = returnAddNum()
println(f(10))
}
程序输出的结果为:
20
示例 2:捕获的变量必须在闭包定义时可见。
func f() {
let x = 99
func f1() {
println(x)
}
let f2 = { =>
println(y) // Error, cannot capture 'y' which is not defined yet
}
let y = 88
f1() // Print 99
f2()
}
示例 3:捕获的变量必须在闭包定义前完成初始化。
func f() {
let x: Int64
func f1() {
println(x) // Error, x is not initialized yet
}
x = 99
f1()
}
如果捕获的变量是引用类型,可修改其可变实例成员变量的值。
class C {
public var num: Int64 = 0
}
func returnIncrementer(): () -> Unit {
let c: C = C()
func incrementer() {
c.num++
}
incrementer
}
main() {
let f = returnIncrementer()
f() // c.num increases by 1
}
为了防止捕获了 var 声明变量的闭包逃逸,这类闭包只能被调用,不能作为一等公民使用,包括不能赋值给变量,不能作为实参或返回值使用,不能直接将闭包的名字作为表达式使用。
func f() {
var x = 1
let y = 2
func g() {
println(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
g // Error, g cannot be used as a return value
}
需要注意的是,捕获具有传递性。如果一个函数 f 调用了捕获 var 变量的函数 g,且 g 捕获的 var 变量不在函数 f 内定义,那么函数 f 同样捕获了 var 变量,此时,f 也不能作为一等公民使用。
以下示例中,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 public var a: Int32 = 0
static public 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()
}
函数调用语法糖
尾随 lambda
尾随 lambda 可以使函数的调用看起来像是语言内置的语法一样,增加语言的可扩展性。
当函数最后一个形参是函数类型,并且函数调用对应的实参是 lambda 时,可以使用尾随 lambda 语法,将 lambda 放在函数调用的尾部,圆括号外面。
例如,下例中定义了一个 myIf 函数,它的第一个参数是 Bool 类型,第二个参数是函数类型。当第一个参数的值为 true 时,返回第二个参数调用后的值,否则返回 0。调用 myIf 时可以像普通函数一样调用,也可以使用尾随 lambda 的方式调用。
func myIf(a: Bool, fn: () -> Int64) {
if(a) {
fn()
} else {
0
}
}
func test() {
myIf(true, { => 100 }) // General function call
myIf(true) { // Trailing closure call
100
}
}
当函数调用有且只有一个 lambda 实参时,还可以省略 (),只写 lambda。
示例:
func f(fn: (Int64) -> Int64) { fn(1) }
func test() {
f { i => i * i }
}
Flow 表达式
流操作符包括两种:表示数据流向的中缀操作符 |> (称为 pipeline)和表示函数组合的中缀操作符 ~> (称为 composition)。
Pipeline 表达式
当需要对输入数据做一系列的处理时,可以使用 pipeline 表达式来简化描述。pipeline 表达式的语法形式如下:e1 |> e2。等价于如下形式的语法糖:let v = e1; e2(v) 。
其中 e2 是函数类型的表达式,e1 的类型是 e2 的参数类型的子类型。
示例:
func inc(x: Array<Int64>): Array<Int64> { // Increasing the value of each element in the array by '1'
let s = x.size
var i = 0
for (e in x where i < s) {
x[i] = e + 1
i++
}
x
}
func sum(y: Array<Int64>): Int64 { // Get the sum of elements in the array
var s = 0
for (j in y) {
s += j
}
s
}
let arr: Array<Int64> = [1, 3, 5]
let res = arr |> inc |> sum // res = 12
Composition 表达式
composition 表达式表示两个单参函数的组合。composition 表达式语法为 f ~> g,等价于 { x => g(f(x)) }。
其中 f,g 均为只有一个参数的函数类型的表达式。
f 和 g 组合,则要求 f(x) 的返回类型是 g(…) 的参数类型的子类型。
示例 1:
func f(x: Int64): Float64 {
Float64(x)
}
func g(x: Float64): Float64 {
x
}
var fg = f ~> g // The same as { x: Int64 => g(f(x)) }
示例 2:
func f(x: Int64): Float64 {
Float64(x)
}
let lambdaComp = ({x: Int64 => x}) ~> f // The same as { x: Int64 => f({x: Int64 => x}(x)) }
示例 3:
func h1<T>(x: T): T { x }
func h2<T>(x: T): T { x }
var hh = h1 ~> h2 // The same as { x: Int64 => h2(h1(x)) }
注意:
表达式 f ~> g 中,会先对 f 求值,然后对 g 求值,最后才会进行函数的组合。
另外,流操作符不能与无默认值的命名形参函数直接一同使用,这是因为无默认值的命名形参函数必须给出命名实参才可以调用。例如:
func f(a!: Int64): Unit {}
var a = 1 |> f // Error
如果需要使用,开发者可以通过 lambda 表达式传入 f 函数的命名实参:
func f(a!: Int64): Unit {}
var x = 1 |> { x: Int64 => f(a: x) } // Ok
由于相同的原因,当 f 的参数有默认值时,直接与流运算符一起使用也是错误的,例如:
func f(a!: Int64 = 2): Unit {}
var a = 1 |> f // Error
但是当命名形参都存在默认值时,不需要给出命名实参也可以调用该函数,函数仅需要传入非命名形参,那么这种函数是可以同流运算符一起使用的,例如:
func f(a: Int64, b!: Int64 = 2): Unit {}
var a = 1 |> f // Ok
当然,如果想要在调用 f 时,为参数 b 传入其他参数,那么也需要借助 lambda 表达式:
func f(a: Int64, b!: Int64 = 2): Unit {}
var a = 1 |> {x: Int64 => f(x, b: 3)} // Ok
函数调用语法糖
尾随 lambda
尾随 lambda 可以使函数的调用看起来像是语言内置的语法一样,增加语言的可扩展性。
当函数最后一个形参是函数类型,并且函数调用对应的实参是 lambda 时,可以使用尾随 lambda 语法,将 lambda 放在函数调用的尾部,圆括号外面。
例如,下例中定义了一个 myIf 函数,它的第一个参数是 Bool 类型,第二个参数是函数类型。当第一个参数的值为 true 时,返回第二个参数调用后的值,否则返回 0。调用 myIf 时可以像普通函数一样调用,也可以使用尾随 lambda 的方式调用。
func myIf(a: Bool, fn: () -> Int64) {
if(a) {
fn()
} else {
0
}
}
func test() {
myIf(true, { => 100 }) // General function call
myIf(true) { // Trailing closure call
100
}
}
当函数调用有且只有一个 lambda 实参时,还可以省略 (),只写 lambda。
示例:
func f(fn: (Int64) -> Int64) { fn(1) }
func test() {
f { i => i * i }
}
Flow 表达式
流操作符包括两种:表示数据流向的中缀操作符 |> (称为 pipeline)和表示函数组合的中缀操作符 ~> (称为 composition)。
Pipeline 表达式
当需要对输入数据做一系列的处理时,可以使用 pipeline 表达式来简化描述。pipeline 表达式的语法形式如下:e1 |> e2。等价于如下形式的语法糖:let v = e1; e2(v) 。
其中 e2 是函数类型的表达式,e1 的类型是 e2 的参数类型的子类型。
示例:
func inc(x: Array<Int64>): Array<Int64> { // Increasing the value of each element in the array by '1'
let s = x.size
var i = 0
for (e in x where i < s) {
x[i] = e + 1
i++
}
x
}
func sum(y: Array<Int64>): Int64 { // Get the sum of elements in the array
var s = 0
for (j in y) {
s += j
}
s
}
let arr: Array<Int64> = [1, 3, 5]
let res = arr |> inc |> sum // res = 12
Composition 表达式
composition 表达式表示两个单参函数的组合。composition 表达式语法为 f ~> g,等价于 { x => g(f(x)) }。
其中 f,g 均为只有一个参数的函数类型的表达式。
f 和 g 组合,则要求 f(x) 的返回类型是 g(…) 的参数类型的子类型。
示例 1:
func f(x: Int64): Float64 {
Float64(x)
}
func g(x: Float64): Float64 {
x
}
var fg = f ~> g // The same as { x: Int64 => g(f(x)) }
示例 2:
func f(x: Int64): Float64 {
Float64(x)
}
let lambdaComp = ({x: Int64 => x}) ~> f // The same as { x: Int64 => f({x: Int64 => x}(x)) }
示例 3:
func h1<T>(x: T): T { x }
func h2<T>(x: T): T { x }
var hh = h1<Int64> ~> h2<Int64> // The same as { x: Int64 => h2<Int64>(h1<Int64>(x)) }
注意:
表达式 f ~> g 中,会先对 f 求值,然后对 g 求值,最后才会进行函数的组合。
另外,流操作符不能与无默认值的命名形参函数直接一同使用,这是因为无默认值的命名形参函数必须给出命名实参才可以调用。例如:
func f(a!: Int64): Unit {}
var a = 1 |> f // Error
如果需要使用,开发者可以通过 lambda 表达式传入 f 函数的命名实参:
func f(a!: Int64): Unit {}
var x = 1 |> { x: Int64 => f(a: x) } // Ok
由于相同的原因,当 f 的参数有默认值时,直接与流运算符一起使用也是错误的,例如:
func f(a!: Int64 = 2): Unit {}
var a = 1 |> f // Error
但是当命名形参都存在默认值时,不需要给出命名实参也可以调用该函数,函数仅需要传入非命名形参,那么这种函数是可以同流运算符一起使用的,例如:
func f(a: Int64, b!: Int64 = 2): Unit {}
var a = 1 |> f // Ok
当然,如果想要在调用 f 时,为参数 b 传入其他参数,那么也需要借助 lambda 表达式:
func f(a: Int64, b!: Int64 = 2): Unit {}
var a = 1 |> {x: Int64 => f(x, b: 3)} // Ok
变长参数
变长参数是一种特殊的函数调用语法糖。当形参最后一个非命名参数是 Array 类型时,实参中对应位置可以直接传入参数序列代替 Array 字面量(参数个数可以是 0 个或多个)。示例如下:
func sum(arr: Array<Int64>) {
var total = 0
for (x in arr) {
total += x
}
return total
}
main() {
println(sum())
println(sum(1, 2, 3))
}
程序输出:
0
6
需要注意,只有最后一个非命名参数可以作为变长参数,命名参数不能使用这个语法糖。
func length(arr!: Array<Int64>) {
return arr.size
}
main() {
println(length()) // Error, expected 1 argument, found 0
println(length(1, 2, 3)) // Error, expected 1 argument, found 3
}
变长参数可以出现在全局函数、静态成员函数、实例成员函数、局部函数、构造函数、函数变量、lambda、函数调用操作符重载、索引操作符重载的调用处。不支持其他操作符重载、composition、pipeline 这几种调用方式。示例如下:
class Counter {
var total = 0
init(data: Array<Int64>) { total = data.size }
operator func ()(data: Array<Int64>) { total += data.size }
}
main() {
let counter = Counter(1, 2)
println(counter.total)
counter(3, 4, 5)
println(counter.total)
}
程序输出:
2
5
函数重载决议总是会优先考虑不使用变长参数就能匹配的函数,只有在所有函数都不能匹配,才尝试使用变长参数解析。示例如下:
func f<T>(x: T) where T <: ToString {
println("item: ${x}")
}
func f(arr: Array<Int64>) {
println("array: ${arr}")
}
main() {
f()
f(1)
f(1, 2)
}
程序输出:
array: []
item: 1
array: [1, 2]
当编译器无法决议时会报错:
func f(arr: Array<Int64>) { arr.size }
func f(first: Int64, arr: Array<Int64>) { first + arr.size }
main() {
println(f(1, 2, 3)) // Error
}
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐


所有评论(0)