- 文档
- 知识库
- 教程
- OpenHarmony
【免费下载链接】CangjieCorpus
本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!
本文围绕仓颉(Cangjie)编程语言三类函数调用语法糖展开:尾随 lambda、以|>(pipeline)与~>(composition)为代表的流操作符表达式,以及变长参数。它们让函数调用"看起来像语言内置语法",显著提升 DSL 与链式数据处理代码的可读性和可扩展性。读完本文,你将掌握每种语法糖的适用条件、类型约束、与命名参数的交互规则及重载决议细节,并能在实战中准确选用。
本文以 function_call_desugar.md 为骨架,并交叉引用 lambda.md、operator.md、generic_function.md、use_option.md 等仓库文档深化佐证。
一、尾随 Lambda:让函数调用"像语法"
1.1 基本规则
当满足以下两个条件时,可以使用**尾随 lambda(trailing lambda)**语法:
- 函数的最后一个形参是函数类型;
- 函数调用时,与该形参对应的实参是一个lambda 表达式。
此时可以把 lambda 从圆括号内挪到函数调用的尾部、圆括号之外,调用形式变为f(args) { lambda }。
示例:定义myIf函数,第一个参数是Bool,第二个参数是() -> Int64函数类型。当条件为true时返回第二个参数调用结果,否则返回0:
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 } }1.2 唯一 lambda 实参可省略圆括号
当函数调用有且只有一个 lambda 实参(即函数只有一个形参且为函数类型)时,可以进一步省略(),只写 lambda 本身:
func f(fn: (Int64) -> Int64) { fn(1) } func test() { f { i => i * i } }1.3 与 Lambda 语法规则的联动
尾随 lambda 与普通 lambda 语法存在一个重要联动:lambda 表达式不管有没有参数,都不可以省略=>,除非它作为尾随 lambda 使用(见 lambda.md)。例如:
var display = { => println("Hello") } // 普通 lambda 必须带 => func f2(lam: () -> Unit) {} let f2Res = f2 { println("World") } // 尾随 lambda 场景下可省略 =>1.4 问号操作符对尾随 lambda 调用的支持
在Option类型体系下,问号操作符?需要与.、()、[]或{}一起使用,其中{}特指尾随 lambda 调用的场景(见 use_option.md)。也就是说,e?{ ... }可对Option包裹的可调用对象做空安全调用,这与尾随 lambda 语法共同构成了安全链式调用的基础。
从语言设计角度看,尾随 lambda 之所以"合法",根源在于仓颉中函数是一等公民,函数类型(如() -> Int64、(Int64) -> Int64)可以自由地作为形参类型出现在参数列表末尾(参见 first_class_citizen.md)。因此,任何末尾参数为函数类型的函数都天然具备尾随 lambda 的调用潜力,这使得自定义控制结构(如上面的myIf)写起来与语言内置语法无异,极大增强了语言的可扩展性。
二、Flow 表达式:Pipeline 与 Composition
流操作符(flow operators)包括两种:
| 操作符 | 名称 | 含义 | 等价语法糖 |
|---|---|---|---|
|> | pipeline | 数据流向 | let v = e1; e2(v) |
~> | composition | 函数组合 | { x => g(f(x)) } |
在仓颉的操作符优先级表中,|>与~>的优先级均为17(数值越大优先级越低,仅高于赋值类操作符),且都是左结合,即e1 |> e2 |> e3按从左到右的顺序依次结合。
2.1 Pipeline 表达式:链式数据流
当需要对输入数据做一系列连续处理时,pipeline表达式可以简化嵌套调用。其语法形式为:
e1 |> e2等价于:
let v = e1; e2(v)其中e2是函数类型的表达式,且要求e1的类型是e2参数类型的子类型。
示例:先对数组每个元素+1,再求和:
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由于|>左结合,arr |> inc |> sum实际按(arr |> inc) |> sum求值,等价于手写sum(inc(arr)),但阅读顺序与数据处理流向完全一致,非常适合表达"先…再…"的多步管道。
2.2 Composition 表达式:组合两个单参函数
composition表达式用于组合两个单参数函数,语法形式为:
f ~> g等价于:
{ x => g(f(x)) }约束条件:
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:与 lambda 组合
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求值,最后才进行函数的组合。因此两侧操作数必须是可求值的表达式,求值顺序有明确保证。
~>与泛型函数结合的场景在仓库中也有实际佐证:generic_function.md 展示了利用泛型函数实现手写composition,以及利用id泛型函数单位元性质验证id<Int64> ~> double与double ~> id<Int64>等价(输出true):
func foo(a: Int64) { func id<T>(a: T): T { a } func double(a: Int64): Int64 { a + a } return (id<Int64> ~> double)(a) == (double ~> id<Int64>)(a) } main() { println(foo(1)) return 0 }2.3 与命名形参的兼容性限制
流操作符不能与"无默认值的命名形参函数"直接一同使用,原因在于:无默认值的命名形参在调用时必须显式给出命名实参(参见 define_functions.md 与 call_functions.md),而 pipeline/composition 表达式右侧直接是函数表达式,无法提供命名实参。
错误示例:
func f(a!: Int64): Unit {} var a = 1 |> f // Error解法一:借助 lambda 传入命名实参
func f(a!: Int64): Unit {} var x = 1 |> { x: Int64 => f(a: x) } // Ok命名形参有默认值时,直接使用仍错误(调用时仍需给出命名实参):
func f(a!: Int64 = 2): Unit {} var a = 1 |> f // Error解法二:全部命名形参均有默认值,函数只需传非命名参数——此时该函数可以直接与流操作符一同使用:
func f(a: Int64, b!: Int64 = 2): Unit {} var a = 1 |> f // Ok若希望同时为有默认值的命名参数b传入其他值,仍需借助 lambda:
func f(a: Int64, b!: Int64 = 2): Unit {} var a = 1 |> {x: Int64 => f(x, b: 3)} // Ok三、变长参数:实参序列自动装箱为 Array
3.1 基本用法
变长参数是一种特殊的函数调用语法糖:当形参的最后一个非命名参数是Array类型时,实参中对应位置可以直接传入参数序列(0 个或多个),代替Array字面量。
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 63.2 使用限制
只有最后一个非命名参数可以作为变长参数,命名参数不能使用这个语法糖。若对命名参数使用,将报错:
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 }3.3 支持与不支持的出现位置
变长参数可以出现在以下函数类型的调用处:
- 全局函数
- 静态成员函数
- 实例成员函数
- 局部函数
- 构造函数
- 函数变量
- lambda
- 函数调用操作符
()的重载(参见 operator_overloading.md) - 索引操作符
[]的重载
示例:构造函数与()操作符重载使用变长参数
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不支持的调用方式:其他操作符重载、composition(~>)、pipeline(|>)。
3.4 重载决议规则
函数重载决议时总是优先考虑不使用变长参数就能匹配的函数,只有当所有函数都无法匹配时,才尝试用变长参数解析:
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]可以看到:
f()只能匹配变长参数版本,输出array: [];f(1)优先匹配非变长的泛型版本f<T>(x: T),输出item: 1;f(1, 2)因非变长版本无法匹配两个实参,回退到变长参数版本,输出array: [1, 2]。
3.5 决议失败的报错场景
当多个变长参数函数都能"部分匹配"、编译器无法唯一决议时会报错。例如下面的f(first: Int64, arr: Array<Int64>)与f(arr: Array<Int64>)都试图把(1, 2, 3)拆成"首参 + 变长序列"或"全变长序列",产生歧义:
func f(arr: Array<Int64>) { arr.size } func f(first: Int64, arr: Array<Int64>) { first + arr.size } main() { println(f(1, 2, 3)) // Error }这也提醒我们:在 API 设计中,应避免让"纯变长参数签名"与"固定参数 + 变长参数签名"同时暴露在重载集合中,以免调用方踩中决议歧义。
四、小结
仓颉的三类函数调用语法糖各司其职,共同提升了表达力:
| 语法糖 | 适用场景 | 关键约束 |
|---|---|---|
| 尾随 lambda | 最后一个形参为函数类型、实参为 lambda | 唯一 lambda 实参时可省略();尾随场景下 lambda 可省略=> |
|>pipeline | 数据多步流水线处理 | e2为函数类型;e1类型须是e2参数类型的子类型 |
~>composition | 组合两个单参函数 | 两侧均为单参函数;f返回类型须是g参数类型的子类型 |
| 变长参数 | 最后一个非命名Array形参 | 仅限非命名参数;支持函数/构造器/()、[]重载调用;重载决议优先非变长版本 |
综合运用时需特别注意两条边界规则:一是流操作符与无默认值命名形参函数不兼容,需用 lambda 中转;二是变长参数不支持composition、pipeline及其他操作符重载调用。掌握这些细节,即可在仓颉项目中写出既简洁又无歧义的函数调用代码。
- 文档
- 知识库
- 教程
- OpenHarmony
【免费下载链接】CangjieCorpus
本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!
相关推荐
仓颉语言函数调用语法糖详解:尾随 Lambda、Pipeline/Composition 流表达式与变长参数
仓颉语言函数调用语法糖详解:尾随 Lambda、Pipeline/Composition 流表达式与变长参数 本篇基于仓颉编程语言(Cangjie)开发者文档中
文档编程语言仓颉编程语言函数调用语法糖深度解析:尾随 lambda、流式操作与变长参数
仓颉编程语言函数调用语法糖深度解析:尾随 lambda、流式操作与变长参数 仓颉(Cangjie)编程语言为函数调用提供了一组贴近函数式编程风格的语法糖: 尾随
文档编程语言《仓颉语言实战》仓颉Lambda表达式、闭包与管道语法糖:函数高级用法完全指南
《仓颉语言实战》仓颉Lambda表达式、闭包与管道语法糖:函数高级用法完全指南 《仓颉语言实战》是张磊编著、清华大学出版社出版的仓颉语言入门教程,本书示例代码库
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考